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,154 @@
|
|
|
1
|
+
"""Effect types for the functional core.
|
|
2
|
+
|
|
3
|
+
Effects represent side effects (I/O operations) that should be
|
|
4
|
+
executed by the shell layer. Core functions return effects instead
|
|
5
|
+
of performing I/O directly, enabling pure testing.
|
|
6
|
+
|
|
7
|
+
This implements the "Effect System" pattern where:
|
|
8
|
+
- Core functions are pure and return descriptions of side effects (Effects)
|
|
9
|
+
- Shell layer executes these effects, performing actual I/O
|
|
10
|
+
|
|
11
|
+
All effects are immutable (frozen dataclasses) and hashable.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Effect(ABC):
|
|
20
|
+
"""Base class for all side effects.
|
|
21
|
+
|
|
22
|
+
Effects describe I/O operations without performing them.
|
|
23
|
+
They are created by core logic and executed by the shell layer.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def describe(self) -> str:
|
|
28
|
+
"""Return human-readable description of this effect."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class WriteFile(Effect):
|
|
34
|
+
"""Effect: Write content to a file.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
path: Target file path
|
|
38
|
+
content: Content to write (string or bytes)
|
|
39
|
+
encoding: Text encoding (used only for string content)
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
path: Path
|
|
44
|
+
content: str | bytes
|
|
45
|
+
encoding: str = "utf-8"
|
|
46
|
+
|
|
47
|
+
def describe(self) -> str:
|
|
48
|
+
"""Return human-readable description."""
|
|
49
|
+
return f"Write file: {self.path}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class MakeDirectory(Effect):
|
|
54
|
+
"""Effect: Create a directory.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
path: Directory path to create
|
|
58
|
+
parents: If True, create parent directories as needed
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
path: Path
|
|
63
|
+
parents: bool = True
|
|
64
|
+
|
|
65
|
+
def describe(self) -> str:
|
|
66
|
+
"""Return human-readable description."""
|
|
67
|
+
return f"Create directory: {self.path}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class DeleteFile(Effect):
|
|
72
|
+
"""Effect: Delete a file.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
path: File path to delete
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
path: Path
|
|
80
|
+
|
|
81
|
+
def describe(self) -> str:
|
|
82
|
+
"""Return human-readable description."""
|
|
83
|
+
return f"Delete file: {self.path}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class OpenBrowser(Effect):
|
|
88
|
+
"""Effect: Open a URL in the default web browser.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
url: URL to open (can be http://, https://, or file://)
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
url: str
|
|
96
|
+
|
|
97
|
+
def describe(self) -> str:
|
|
98
|
+
"""Return human-readable description."""
|
|
99
|
+
return f"Open browser: {self.url}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class RunCommand(Effect):
|
|
104
|
+
"""Effect: Execute a shell command.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
command: Command to run as a list of arguments
|
|
108
|
+
cwd: Working directory for command execution (None for current dir)
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
command: list[str]
|
|
113
|
+
cwd: Path | None = None
|
|
114
|
+
|
|
115
|
+
def describe(self) -> str:
|
|
116
|
+
"""Return human-readable description."""
|
|
117
|
+
command_str = " ".join(self.command)
|
|
118
|
+
return f"Run command: {command_str}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True)
|
|
122
|
+
class CopyFile(Effect):
|
|
123
|
+
"""Effect: Copy a file from source to destination.
|
|
124
|
+
|
|
125
|
+
Attributes:
|
|
126
|
+
source: Source file path
|
|
127
|
+
destination: Destination file path
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
source: Path
|
|
132
|
+
destination: Path
|
|
133
|
+
|
|
134
|
+
def describe(self) -> str:
|
|
135
|
+
"""Return human-readable description."""
|
|
136
|
+
return f"Copy file: {self.source} -> {self.destination}"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class RenderPdf(Effect):
|
|
141
|
+
"""Effect: Render HTML+CSS to PDF at the target path.
|
|
142
|
+
|
|
143
|
+
The shell layer is responsible for providing the rendering engine
|
|
144
|
+
(e.g., WeasyPrint).
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
html: str
|
|
148
|
+
css: str
|
|
149
|
+
output_path: Path
|
|
150
|
+
base_url: str | None = None
|
|
151
|
+
|
|
152
|
+
def describe(self) -> str:
|
|
153
|
+
"""Return human-readable description."""
|
|
154
|
+
return f"Render PDF to: {self.output_path}"
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Core exception types for simple-resume."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimpleResumeError(Exception):
|
|
9
|
+
"""Raise for any simple-resume specific error."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
*,
|
|
15
|
+
context: dict[str, Any] | None = None,
|
|
16
|
+
filename: str | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Initialize the exception.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
message: The error message.
|
|
22
|
+
context: Optional context for the error.
|
|
23
|
+
filename: The name of the file being processed.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.message = message
|
|
28
|
+
self.context = context or {}
|
|
29
|
+
self.filename = filename
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
"""Return a formatted error message."""
|
|
33
|
+
base_msg = self.message
|
|
34
|
+
if self.filename:
|
|
35
|
+
base_msg = f"{self.filename}: {base_msg}"
|
|
36
|
+
if self.context:
|
|
37
|
+
context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
|
|
38
|
+
base_msg = f"{base_msg} (context: {context_str})"
|
|
39
|
+
return base_msg
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ValidationError(SimpleResumeError, ValueError):
|
|
43
|
+
"""Raise when resume data validation fails."""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
message: str,
|
|
48
|
+
*,
|
|
49
|
+
errors: list[str] | None = None,
|
|
50
|
+
warnings: list[str] | None = None,
|
|
51
|
+
context: dict[str, Any] | None = None,
|
|
52
|
+
filename: str | None = None,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the exception.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
message: The error message.
|
|
59
|
+
errors: A list of validation errors.
|
|
60
|
+
warnings: A list of validation warnings.
|
|
61
|
+
context: Optional context for the error.
|
|
62
|
+
filename: The name of the file being processed.
|
|
63
|
+
**kwargs: Additional context (will be filtered).
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
# Filter out parameters that should be passed to parent
|
|
67
|
+
filtered_kwargs = {
|
|
68
|
+
k: v for k, v in kwargs.items() if k not in ["context", "filename"]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
super().__init__(message, context=context, filename=filename, **filtered_kwargs)
|
|
72
|
+
self.errors = errors or []
|
|
73
|
+
self.warnings = warnings or []
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ConfigurationError(SimpleResumeError):
|
|
77
|
+
"""Raise when configuration is invalid."""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
message: str,
|
|
82
|
+
*,
|
|
83
|
+
config_key: str | None = None,
|
|
84
|
+
config_value: Any | None = None,
|
|
85
|
+
context: dict[str, Any] | None = None,
|
|
86
|
+
filename: str | None = None,
|
|
87
|
+
**kwargs: Any,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Initialize the exception.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
message: The error message.
|
|
93
|
+
config_key: The configuration key that caused the error.
|
|
94
|
+
config_value: The value of the configuration key.
|
|
95
|
+
context: Optional context for the error.
|
|
96
|
+
filename: The name of the file being processed.
|
|
97
|
+
**kwargs: Additional context (will be filtered).
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
# Filter out parameters that should be passed to parent
|
|
101
|
+
filtered_kwargs = {
|
|
102
|
+
k: v for k, v in kwargs.items() if k not in ["context", "filename"]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
super().__init__(message, context=context, filename=filename, **filtered_kwargs)
|
|
106
|
+
self.config_key = config_key
|
|
107
|
+
self.config_value = config_value
|
|
108
|
+
|
|
109
|
+
def __str__(self) -> str:
|
|
110
|
+
"""Return a formatted error message."""
|
|
111
|
+
base_msg = super().__str__()
|
|
112
|
+
if self.config_key:
|
|
113
|
+
base_msg = f"{base_msg} (config_key={self.config_key})"
|
|
114
|
+
return base_msg
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class PaletteError(SimpleResumeError):
|
|
118
|
+
"""Raise when color palette operations fail."""
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
message: str,
|
|
123
|
+
*,
|
|
124
|
+
palette_name: str | None = None,
|
|
125
|
+
color_values: list[str] | None = None,
|
|
126
|
+
context: dict[str, Any] | None = None,
|
|
127
|
+
filename: str | None = None,
|
|
128
|
+
**kwargs: Any,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Initialize the exception.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
message: The error message.
|
|
134
|
+
palette_name: The name of the palette.
|
|
135
|
+
color_values: The color values that caused the error.
|
|
136
|
+
context: Optional context for the error.
|
|
137
|
+
filename: The name of the file being processed.
|
|
138
|
+
**kwargs: Additional context (will be filtered).
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
# Filter out parameters that should be passed to parent
|
|
142
|
+
filtered_kwargs = {
|
|
143
|
+
k: v for k, v in kwargs.items() if k not in ["context", "filename"]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
super().__init__(message, context=context, filename=filename, **filtered_kwargs)
|
|
147
|
+
self.palette_name = palette_name
|
|
148
|
+
self.color_values = color_values
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FileSystemError(SimpleResumeError):
|
|
152
|
+
"""Raise when file system operations fail."""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
message: str,
|
|
157
|
+
*,
|
|
158
|
+
path: str | None = None,
|
|
159
|
+
operation: str | None = None,
|
|
160
|
+
**kwargs: Any,
|
|
161
|
+
):
|
|
162
|
+
"""Initialize the exception.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
message: The error message.
|
|
166
|
+
path: The file path.
|
|
167
|
+
operation: The file system operation that failed.
|
|
168
|
+
**kwargs: Additional context.
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
super().__init__(message, **kwargs)
|
|
172
|
+
self.path = path
|
|
173
|
+
self.operation = operation
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class SessionError(SimpleResumeError):
|
|
177
|
+
"""Raise when session operations fail."""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self, message: str, *, session_id: str | None = None, **kwargs: Any
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Initialize the exception.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
message: The error message.
|
|
186
|
+
session_id: The ID of the session.
|
|
187
|
+
**kwargs: Additional context.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
super().__init__(message, **kwargs)
|
|
191
|
+
self.session_id = session_id
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class GenerationError(SimpleResumeError):
|
|
195
|
+
"""Raise when PDF/HTML generation fails."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, message: str, **metadata: Any) -> None:
|
|
198
|
+
"""Initialize the exception with optional metadata.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
message: The error message.
|
|
202
|
+
**metadata: Optional metadata such as ``output_path``, ``format_type``,
|
|
203
|
+
``resume_name``, ``context`` and ``filename``.
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
output_path = metadata.pop("output_path", None)
|
|
207
|
+
format_type = metadata.pop("format_type", None)
|
|
208
|
+
resume_name = metadata.pop("resume_name", None)
|
|
209
|
+
context = metadata.pop("context", None)
|
|
210
|
+
filename = metadata.pop("filename", None)
|
|
211
|
+
|
|
212
|
+
if filename is None and resume_name is not None:
|
|
213
|
+
filename = resume_name
|
|
214
|
+
|
|
215
|
+
super().__init__(message, context=context, filename=filename, **metadata)
|
|
216
|
+
self.output_path = str(output_path) if output_path is not None else None
|
|
217
|
+
self.format_type = format_type
|
|
218
|
+
self.resume_name = resume_name
|
|
219
|
+
|
|
220
|
+
def __str__(self) -> str:
|
|
221
|
+
"""Return a formatted error message."""
|
|
222
|
+
base_msg = super().__str__()
|
|
223
|
+
if self.format_type:
|
|
224
|
+
base_msg = f"{base_msg} (format={self.format_type})"
|
|
225
|
+
return base_msg
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TemplateError(SimpleResumeError):
|
|
229
|
+
"""Raise when template processing fails."""
|
|
230
|
+
|
|
231
|
+
def __init__(self, message: str, **metadata: Any) -> None:
|
|
232
|
+
"""Initialize the exception with optional metadata.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
message: The error message.
|
|
236
|
+
**metadata: Optional metadata such as ``template_name``,
|
|
237
|
+
``template_path``, ``context`` and ``filename``.
|
|
238
|
+
|
|
239
|
+
"""
|
|
240
|
+
template_name = metadata.pop("template_name", None)
|
|
241
|
+
template_path = metadata.pop("template_path", None)
|
|
242
|
+
context = metadata.pop("context", None)
|
|
243
|
+
filename = metadata.pop("filename", None)
|
|
244
|
+
|
|
245
|
+
super().__init__(message, context=context, filename=filename, **metadata)
|
|
246
|
+
self.template_name = template_name
|
|
247
|
+
self.template_path = template_path
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
__all__ = [
|
|
251
|
+
# Base exception
|
|
252
|
+
"SimpleResumeError",
|
|
253
|
+
# Specific exception types
|
|
254
|
+
"ConfigurationError",
|
|
255
|
+
"FileSystemError",
|
|
256
|
+
"GenerationError",
|
|
257
|
+
"PaletteError",
|
|
258
|
+
"SessionError",
|
|
259
|
+
"TemplateError",
|
|
260
|
+
"ValidationError",
|
|
261
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Core file operations for resume management.
|
|
2
|
+
|
|
3
|
+
Pure functions for file discovery and path operations without external dependencies.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find_yaml_files(input_dir: Path, pattern: str = "*") -> list[Path]:
|
|
11
|
+
"""Find resume input files matching the given pattern.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
input_dir: Directory to search for YAML files.
|
|
15
|
+
pattern: Glob pattern for matching files.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
List of matching YAML file paths.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
if not input_dir.exists():
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
yaml_files = []
|
|
25
|
+
|
|
26
|
+
# Find files matching pattern with .yaml/.yml/.json extension
|
|
27
|
+
for ext in ("yaml", "yml", "json"):
|
|
28
|
+
for file_path in input_dir.glob(f"{pattern}.{ext}"):
|
|
29
|
+
if file_path.is_file():
|
|
30
|
+
yaml_files.append(file_path)
|
|
31
|
+
|
|
32
|
+
return sorted(yaml_files)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def iterate_yaml_files(
|
|
36
|
+
input_dir: Path, pattern: str = "*"
|
|
37
|
+
) -> Generator[Path, None, None]:
|
|
38
|
+
"""Iterate over YAML files matching the given pattern.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
input_dir: Directory to search for YAML files.
|
|
42
|
+
pattern: Glob pattern for matching files.
|
|
43
|
+
|
|
44
|
+
Yields:
|
|
45
|
+
YAML file paths.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
yield from find_yaml_files(input_dir, pattern)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_resume_name_from_path(file_path: Path) -> str:
|
|
52
|
+
"""Extract resume name from file path.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
file_path: Path to YAML file.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Resume name (filename without extension).
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
return file_path.stem
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"find_yaml_files",
|
|
66
|
+
"iterate_yaml_files",
|
|
67
|
+
"get_resume_name_from_path",
|
|
68
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Core generation functionality for resumes.
|
|
2
|
+
|
|
3
|
+
This module provides pure functions for generating different output formats
|
|
4
|
+
from resume data without any I/O side effects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from simple_resume.core.generate.html import create_html_generator_factory
|
|
10
|
+
from simple_resume.core.generate.pdf import (
|
|
11
|
+
prepare_pdf_with_latex,
|
|
12
|
+
prepare_pdf_with_weasyprint,
|
|
13
|
+
)
|
|
14
|
+
from simple_resume.core.generate.plan import build_generation_plan
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"build_generation_plan",
|
|
18
|
+
"create_html_generator_factory",
|
|
19
|
+
"prepare_pdf_with_latex",
|
|
20
|
+
"prepare_pdf_with_weasyprint",
|
|
21
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Exception types used by the generation subsystem."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from simple_resume.core.exceptions import SimpleResumeError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GenerationError(SimpleResumeError):
|
|
11
|
+
"""Raise when PDF/HTML generation fails."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str, **metadata: Any) -> None:
|
|
14
|
+
"""Initialize the exception with optional metadata.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
message: The error message.
|
|
18
|
+
**metadata: Optional metadata such as ``output_path``, ``format_type``,
|
|
19
|
+
``resume_name``, ``context`` and ``filename``.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
output_path = metadata.pop("output_path", None)
|
|
23
|
+
format_type = metadata.pop("format_type", None)
|
|
24
|
+
resume_name = metadata.pop("resume_name", None)
|
|
25
|
+
context = metadata.pop("context", None)
|
|
26
|
+
filename = metadata.pop("filename", None)
|
|
27
|
+
|
|
28
|
+
if filename is None and resume_name is not None:
|
|
29
|
+
filename = resume_name
|
|
30
|
+
|
|
31
|
+
super().__init__(message, context=context, filename=filename, **metadata)
|
|
32
|
+
self.output_path = str(output_path) if output_path is not None else None
|
|
33
|
+
self.format_type = format_type
|
|
34
|
+
self.resume_name = resume_name
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
"""Return a formatted error message."""
|
|
38
|
+
base_msg = super().__str__()
|
|
39
|
+
if self.format_type:
|
|
40
|
+
base_msg = f"{base_msg} (format={self.format_type})"
|
|
41
|
+
return base_msg
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TemplateError(SimpleResumeError):
|
|
45
|
+
"""Raise when template processing fails."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, message: str, **metadata: Any) -> None:
|
|
48
|
+
"""Initialize the exception with optional metadata.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: The error message.
|
|
52
|
+
**metadata: Optional metadata such as ``template_name``,
|
|
53
|
+
``template_path``, ``context`` and ``filename``.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
template_name = metadata.pop("template_name", None)
|
|
57
|
+
template_path = metadata.pop("template_path", None)
|
|
58
|
+
context = metadata.pop("context", None)
|
|
59
|
+
filename = metadata.pop("filename", None)
|
|
60
|
+
|
|
61
|
+
super().__init__(message, context=context, filename=filename, **metadata)
|
|
62
|
+
self.template_name = template_name
|
|
63
|
+
self.template_path = template_path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"GenerationError",
|
|
68
|
+
"TemplateError",
|
|
69
|
+
]
|