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,226 @@
1
+ """Pure result objects for simple-resume operations.
2
+
3
+ This module contains immutable data classes that describe generation results.
4
+ These are pure data structures with NO I/O operations - all file operations
5
+ should be performed by the shell layer.
6
+
7
+ Following the functional core / imperative shell pattern:
8
+ - Core: Describes what was generated (this module)
9
+ - Shell: Performs I/O operations on the results (shell/file_opener.py)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import time
16
+ from collections.abc import Iterator
17
+ from dataclasses import dataclass, field
18
+ from pathlib import PosixPath, PurePath
19
+ from typing import Any
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class GenerationMetadata:
24
+ """Metadata describing a generation operation (pure data)."""
25
+
26
+ format_type: str
27
+ template_name: str
28
+ generation_time: float
29
+ file_size: int
30
+ resume_name: str
31
+ palette_info: dict[str, Any] | None = None
32
+ page_count: int | None = None
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class GenerationResult:
37
+ """Pure generation result - describes the artifact location.
38
+
39
+ This is a pure data class that only holds information about a generated file.
40
+ It does NOT perform any I/O operations. For file operations like opening,
41
+ deleting, or reading files, use the shell layer (shell/file_opener.py).
42
+
43
+ Attributes:
44
+ output_path: Path where the file was generated
45
+ format_type: Type of the generated file ('pdf', 'html', etc.)
46
+ metadata: Optional metadata about the generation process
47
+
48
+ """
49
+
50
+ output_path: Any
51
+ _normalized_path: PosixPath = field(init=False, repr=False, compare=False)
52
+ format_type: str
53
+ metadata: GenerationMetadata | None = None
54
+
55
+ def __post_init__(self) -> None:
56
+ """Initialize the GenerationResult after dataclass creation."""
57
+ original_path = self.output_path
58
+ try:
59
+ if isinstance(original_path, PurePath):
60
+ normalized_path = PosixPath(os.fspath(original_path))
61
+ else:
62
+ # Force POSIX concrete path to avoid WindowsPath instantiation
63
+ # when platform is mocked during CI strategy tests.
64
+ normalized_path = PosixPath(os.fspath(original_path))
65
+ except Exception:
66
+ # Final safety net: force a POSIX path from string.
67
+ normalized_path = PosixPath(str(original_path))
68
+
69
+ object.__setattr__(self, "_normalized_path", normalized_path)
70
+
71
+ # Normalize format_type to lowercase
72
+ normalized_format = self.format_type.lower()
73
+ object.__setattr__(self, "format_type", normalized_format)
74
+
75
+ # Create default metadata if none provided
76
+ if self.metadata is None:
77
+ default_metadata = GenerationMetadata(
78
+ format_type=normalized_format,
79
+ template_name="unknown",
80
+ generation_time=time.time(),
81
+ file_size=0,
82
+ resume_name="unknown",
83
+ palette_info=None,
84
+ page_count=None,
85
+ )
86
+ object.__setattr__(self, "metadata", default_metadata)
87
+
88
+ def __repr__(self) -> str: # pragma: no cover - debugging helper
89
+ """Return a detailed string representation for debugging."""
90
+ return (
91
+ f"GenerationResult(output_path={self.output_path}, "
92
+ f"format_type={self.format_type})"
93
+ )
94
+
95
+ def __str__(self) -> str:
96
+ """Return a string representation of the GenerationResult."""
97
+ return f"GenerationResult(format={self.format_type}, path={self.output_path})"
98
+
99
+ @property
100
+ def name(self) -> str:
101
+ """Return the filename of the output file."""
102
+ return self._normalized_path.name
103
+
104
+ @property
105
+ def stem(self) -> str:
106
+ """Return the stem (filename without extension) of the output file."""
107
+ return self._normalized_path.stem
108
+
109
+ @property
110
+ def suffix(self) -> str:
111
+ """Return the suffix (extension) of the output file."""
112
+ return self._normalized_path.suffix
113
+
114
+ @property
115
+ def exists(self) -> bool:
116
+ """Check if the output file exists and is a file (not a directory).
117
+
118
+ Note: This is a read-only property that checks file existence.
119
+ It's acceptable in core because it's a pure query with no side effects.
120
+ """
121
+ return self._normalized_path.exists() and self._normalized_path.is_file()
122
+
123
+ @property
124
+ def size(self) -> int:
125
+ """Return file size in bytes (0 if file doesn't exist).
126
+
127
+ Note: This is a read-only property that queries file metadata.
128
+ It's acceptable in core because it's a pure query with no side effects.
129
+ """
130
+ try:
131
+ return self._normalized_path.stat().st_size if self.exists else 0
132
+ except OSError:
133
+ return 0
134
+
135
+ @property
136
+ def size_human(self) -> str:
137
+ """Return file size in human-readable format."""
138
+ size = self.size
139
+ KB_FACTOR = 1024
140
+ MB_FACTOR = 1024 * 1024
141
+ GB_FACTOR = 1024 * 1024 * 1024
142
+ if size < KB_FACTOR:
143
+ return f"{size} B"
144
+ elif size < MB_FACTOR:
145
+ return f"{size / KB_FACTOR:.1f} KB"
146
+ elif size < GB_FACTOR:
147
+ return f"{size / MB_FACTOR:.1f} MB"
148
+ else:
149
+ return f"{size / GB_FACTOR:.1f} GB"
150
+
151
+ def __bool__(self) -> bool:
152
+ """Return True if the output file exists."""
153
+ return self.exists
154
+
155
+
156
+ @dataclass(frozen=True)
157
+ class BatchGenerationResult:
158
+ """Pure batch result with aggregate reporting information.
159
+
160
+ This is a pure data class that holds multiple GenerationResults.
161
+ It does NOT perform any I/O operations directly.
162
+ """
163
+
164
+ results: dict[str, GenerationResult] = field(default_factory=dict)
165
+ resume_name: str | None = None
166
+ formats: list[str] | None = None
167
+ total_time: float = 0.0
168
+ successful: int = 0
169
+ failed: int = 0
170
+ errors: dict[str, Exception] = field(default_factory=dict)
171
+
172
+ @property
173
+ def total(self) -> int:
174
+ """Return total number of operations."""
175
+ return self.successful + self.failed
176
+
177
+ @property
178
+ def success_rate(self) -> float:
179
+ """Return success rate as a percentage."""
180
+ total = self.total
181
+ if total == 0:
182
+ return 0.0
183
+ return (self.successful / total) * 100.0
184
+
185
+ def __iter__(self) -> Iterator[GenerationResult]:
186
+ """Return an iterator over the results."""
187
+ if self.results is None:
188
+ return iter([])
189
+ return iter(self.results.values())
190
+
191
+ def __len__(self) -> int:
192
+ """Return the number of results."""
193
+ return len(self.results) if self.results is not None else 0
194
+
195
+ def __getitem__(self, key: str) -> GenerationResult:
196
+ """Return the result with the given key."""
197
+ if self.results is None:
198
+ raise KeyError(f"No results available - key '{key}' not found")
199
+ return self.results[key]
200
+
201
+ def __contains__(self, key: str) -> bool:
202
+ """Check if a result with the given key exists."""
203
+ return self.results is not None and key in self.results
204
+
205
+ def get(
206
+ self, key: str, default: GenerationResult | None = None
207
+ ) -> GenerationResult | None:
208
+ """Get a result by key, returning default if not found."""
209
+ if self.results is None:
210
+ return default
211
+ return self.results.get(key, default)
212
+
213
+ def get_successful(self) -> list[GenerationResult]:
214
+ """Return the list of successful results."""
215
+ return list(self.results.values()) if self.results is not None else []
216
+
217
+ def get_failed(self) -> dict[str, Exception]:
218
+ """Return the dictionary of failed results."""
219
+ return self.errors.copy() if self.errors is not None else {}
220
+
221
+
222
+ __all__ = [
223
+ "GenerationMetadata",
224
+ "GenerationResult",
225
+ "BatchGenerationResult",
226
+ ]