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,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command line helpers for palette discovery."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from simple_resume.shell.palettes.loader import (
|
|
11
|
+
build_palettable_snapshot,
|
|
12
|
+
get_palette_registry,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cmd_snapshot(args: argparse.Namespace) -> int:
|
|
17
|
+
"""Export current palettable registry snapshot to JSON."""
|
|
18
|
+
snapshot = build_palettable_snapshot()
|
|
19
|
+
output = json.dumps(snapshot, indent=2)
|
|
20
|
+
if args.output:
|
|
21
|
+
args.output.write(output)
|
|
22
|
+
args.output.write("\n")
|
|
23
|
+
else:
|
|
24
|
+
print(output)
|
|
25
|
+
return 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_list(_: argparse.Namespace) -> int:
|
|
29
|
+
"""List all available palette names with preview colors."""
|
|
30
|
+
registry = get_palette_registry()
|
|
31
|
+
for palette in registry.list():
|
|
32
|
+
print(f"{palette.name}: {', '.join(palette.swatches[:6])}")
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
37
|
+
"""Build argument parser for palette CLI."""
|
|
38
|
+
parser = argparse.ArgumentParser(description="Palette utilities")
|
|
39
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
40
|
+
|
|
41
|
+
snap = subparsers.add_parser("snapshot", help="Build palettable snapshot")
|
|
42
|
+
snap.add_argument(
|
|
43
|
+
"-o",
|
|
44
|
+
"--output",
|
|
45
|
+
type=argparse.FileType("w", encoding="utf-8"),
|
|
46
|
+
help="Output file (defaults to stdout)",
|
|
47
|
+
)
|
|
48
|
+
snap.set_defaults(func=cmd_snapshot)
|
|
49
|
+
|
|
50
|
+
list_cmd = subparsers.add_parser("list", help="List available palettes")
|
|
51
|
+
list_cmd.set_defaults(func=cmd_list)
|
|
52
|
+
|
|
53
|
+
return parser
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main(argv: list[str] | None = None) -> int:
|
|
57
|
+
"""Run the palette CLI with given arguments."""
|
|
58
|
+
parser = build_parser()
|
|
59
|
+
args = parser.parse_args(argv)
|
|
60
|
+
result = args.func(args)
|
|
61
|
+
return int(result) if result is not None else 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def snapshot() -> None:
|
|
65
|
+
"""Entry point for palette-snapshot command."""
|
|
66
|
+
sys.exit(main(["snapshot"]))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def palette_list() -> None:
|
|
70
|
+
"""Entry point for palette-list command."""
|
|
71
|
+
sys.exit(main(["list"]))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
sys.exit(main())
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Provide a CLI entry point for generating randomized palette demo YAML files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import random
|
|
7
|
+
import secrets
|
|
8
|
+
import string
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from oyaml import safe_dump, safe_load
|
|
12
|
+
|
|
13
|
+
from simple_resume.core.markdown import derive_bold_color
|
|
14
|
+
from simple_resume.core.palettes.generators import generate_hcl_palette
|
|
15
|
+
from simple_resume.shell.palettes.loader import get_palette_registry
|
|
16
|
+
|
|
17
|
+
DEFAULT_OUTPUT = Path("sample/input/sample_palette_demo_random.yaml")
|
|
18
|
+
DEFAULT_TEMPLATE = Path("sample/input/sample_palette_demo.yaml")
|
|
19
|
+
|
|
20
|
+
# Realistic word banks for generating resume content
|
|
21
|
+
FIRST_NAMES = [
|
|
22
|
+
"Alex",
|
|
23
|
+
"Jordan",
|
|
24
|
+
"Morgan",
|
|
25
|
+
"Taylor",
|
|
26
|
+
"Casey",
|
|
27
|
+
"Riley",
|
|
28
|
+
"Quinn",
|
|
29
|
+
"Avery",
|
|
30
|
+
"Cameron",
|
|
31
|
+
"Dakota",
|
|
32
|
+
"Emerson",
|
|
33
|
+
"Harper",
|
|
34
|
+
"Hayden",
|
|
35
|
+
"Jamie",
|
|
36
|
+
"Kendall",
|
|
37
|
+
"Logan",
|
|
38
|
+
"Parker",
|
|
39
|
+
"Reese",
|
|
40
|
+
"Sage",
|
|
41
|
+
"Skylar",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
LAST_NAMES = [
|
|
45
|
+
"Anderson",
|
|
46
|
+
"Bennett",
|
|
47
|
+
"Carter",
|
|
48
|
+
"Davis",
|
|
49
|
+
"Ellis",
|
|
50
|
+
"Foster",
|
|
51
|
+
"Garcia",
|
|
52
|
+
"Hayes",
|
|
53
|
+
"Jackson",
|
|
54
|
+
"Kim",
|
|
55
|
+
"Lee",
|
|
56
|
+
"Martinez",
|
|
57
|
+
"Nelson",
|
|
58
|
+
"Parker",
|
|
59
|
+
"Rivera",
|
|
60
|
+
"Santos",
|
|
61
|
+
"Taylor",
|
|
62
|
+
"Williams",
|
|
63
|
+
"Zhang",
|
|
64
|
+
"Chen",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
COMPANIES = [
|
|
68
|
+
"TechCorp",
|
|
69
|
+
"DataSystems",
|
|
70
|
+
"CloudWorks",
|
|
71
|
+
"InnovateLabs",
|
|
72
|
+
"DevStack",
|
|
73
|
+
"CodeCraft",
|
|
74
|
+
"StreamFlow",
|
|
75
|
+
"PixelPerfect",
|
|
76
|
+
"ByteBridge",
|
|
77
|
+
"Quantum Dynamics",
|
|
78
|
+
"NexGen Solutions",
|
|
79
|
+
"Vertex Technologies",
|
|
80
|
+
"Horizon Digital",
|
|
81
|
+
"Pulse Networks",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
TECH_SKILLS = [
|
|
85
|
+
"API design",
|
|
86
|
+
"microservices architecture",
|
|
87
|
+
"cloud infrastructure",
|
|
88
|
+
"CI/CD pipelines",
|
|
89
|
+
"performance optimization",
|
|
90
|
+
"system monitoring",
|
|
91
|
+
"database design",
|
|
92
|
+
"caching strategies",
|
|
93
|
+
"load balancing",
|
|
94
|
+
"automated testing",
|
|
95
|
+
"containerization",
|
|
96
|
+
"orchestration",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
ACCOMPLISHMENTS = [
|
|
100
|
+
"Reduced latency by {percent}% through optimization",
|
|
101
|
+
"Increased test coverage from {low}% to {high}%",
|
|
102
|
+
"Led migration of {count} services to cloud infrastructure",
|
|
103
|
+
"Improved deployment frequency by {mult}x using automation",
|
|
104
|
+
"Architected scalable solution handling {volume} requests/day",
|
|
105
|
+
"Mentored team of {size} engineers on best practices",
|
|
106
|
+
"Reduced operational costs by {percent}% through efficient resource allocation",
|
|
107
|
+
"Implemented monitoring system tracking {metric_count}+ metrics",
|
|
108
|
+
"Streamlined development workflow reducing build time by {percent}%",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
PROJECT_DESCRIPTIONS = [
|
|
112
|
+
"Designed and implemented {feature} using {tech_stack}",
|
|
113
|
+
"Built {system_type} to handle {capability}",
|
|
114
|
+
"Created automated {process} reducing manual effort by {percent}%",
|
|
115
|
+
"Developed {feature} integrating with {platform}",
|
|
116
|
+
"Optimized {component} achieving {improvement}",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
FEATURES = [
|
|
120
|
+
"real-time analytics dashboard",
|
|
121
|
+
"payment processing system",
|
|
122
|
+
"user authentication service",
|
|
123
|
+
"data pipeline",
|
|
124
|
+
"API gateway",
|
|
125
|
+
"notification system",
|
|
126
|
+
"search engine",
|
|
127
|
+
"content delivery network",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
TECH_STACKS = [
|
|
131
|
+
"Python and FastAPI",
|
|
132
|
+
"Node.js and Express",
|
|
133
|
+
"Go and gRPC",
|
|
134
|
+
"Java and Spring Boot",
|
|
135
|
+
"React and TypeScript",
|
|
136
|
+
"Vue.js and Nuxt",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
IMPROVEMENTS = [
|
|
140
|
+
"99.9% uptime",
|
|
141
|
+
"sub-100ms response times",
|
|
142
|
+
"zero-downtime deployments",
|
|
143
|
+
"10x throughput increase",
|
|
144
|
+
"50% cost reduction",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _random_words(count: int, *, word_len: int = 5) -> list[str]:
|
|
149
|
+
"""Generate a list of deterministic-length lowercase words."""
|
|
150
|
+
if count <= 0:
|
|
151
|
+
return []
|
|
152
|
+
alphabet = string.ascii_lowercase
|
|
153
|
+
return [
|
|
154
|
+
"".join(secrets.choice(alphabet) for _ in range(max(1, word_len)))
|
|
155
|
+
for _ in range(count)
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _random_name() -> str:
|
|
160
|
+
"""Generate a realistic random name."""
|
|
161
|
+
first = secrets.choice(FIRST_NAMES)
|
|
162
|
+
last = secrets.choice(LAST_NAMES)
|
|
163
|
+
return f"{first} {last}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _random_sentence(context: str = "general") -> str:
|
|
167
|
+
"""Generate a realistic sentence based on context."""
|
|
168
|
+
if context == "accomplishment":
|
|
169
|
+
template = secrets.choice(ACCOMPLISHMENTS)
|
|
170
|
+
return template.format(
|
|
171
|
+
percent=secrets.randbelow(50) + 30,
|
|
172
|
+
low=secrets.randbelow(30) + 40,
|
|
173
|
+
high=secrets.randbelow(20) + 80,
|
|
174
|
+
count=secrets.randbelow(15) + 5,
|
|
175
|
+
mult=secrets.randbelow(5) + 2,
|
|
176
|
+
volume=f"{secrets.randbelow(900) + 100}K",
|
|
177
|
+
size=secrets.randbelow(8) + 3,
|
|
178
|
+
metric_count=secrets.randbelow(50) + 50,
|
|
179
|
+
)
|
|
180
|
+
elif context == "project":
|
|
181
|
+
template = secrets.choice(PROJECT_DESCRIPTIONS)
|
|
182
|
+
return template.format(
|
|
183
|
+
feature=secrets.choice(FEATURES),
|
|
184
|
+
tech_stack=secrets.choice(TECH_STACKS),
|
|
185
|
+
system_type=secrets.choice(
|
|
186
|
+
["distributed system", "service", "platform", "framework"]
|
|
187
|
+
),
|
|
188
|
+
capability=secrets.choice(
|
|
189
|
+
[
|
|
190
|
+
"high-volume traffic",
|
|
191
|
+
"real-time processing",
|
|
192
|
+
"multi-tenant operations",
|
|
193
|
+
]
|
|
194
|
+
),
|
|
195
|
+
percent=secrets.randbelow(50) + 30,
|
|
196
|
+
process=secrets.choice(
|
|
197
|
+
["deployment pipeline", "testing suite", "code review process"]
|
|
198
|
+
),
|
|
199
|
+
platform=secrets.choice(["AWS", "GCP", "Azure", "Kubernetes"]),
|
|
200
|
+
component=secrets.choice(
|
|
201
|
+
[
|
|
202
|
+
"database queries",
|
|
203
|
+
"API endpoints",
|
|
204
|
+
"frontend rendering",
|
|
205
|
+
]
|
|
206
|
+
),
|
|
207
|
+
improvement=secrets.choice(IMPROVEMENTS),
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
# General technical description
|
|
211
|
+
skill = secrets.choice(TECH_SKILLS)
|
|
212
|
+
return f"Experienced with {skill} and modern development practices."
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _random_description(paragraphs: int = 2) -> str:
|
|
216
|
+
"""Generate a realistic multi-paragraph description."""
|
|
217
|
+
paras = []
|
|
218
|
+
for _ in range(paragraphs):
|
|
219
|
+
sentences = []
|
|
220
|
+
for _ in range(2):
|
|
221
|
+
sentences.append(_random_sentence("general"))
|
|
222
|
+
paras.append(" ".join(sentences))
|
|
223
|
+
return "\n\n".join(paras)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _random_email(name: str) -> str:
|
|
227
|
+
"""Generate a random email address for the given name."""
|
|
228
|
+
handle = name.lower().replace(" ", ".")
|
|
229
|
+
suffix = "".join(
|
|
230
|
+
secrets.choice(string.ascii_lowercase + string.digits) for _ in range(8)
|
|
231
|
+
)
|
|
232
|
+
return f"{handle}.{suffix}@example.com"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _random_linkedin(name: str) -> str:
|
|
236
|
+
"""Generate a random LinkedIn profile URL for the given name."""
|
|
237
|
+
handle = name.lower().replace(" ", "-")
|
|
238
|
+
suffix = "".join(
|
|
239
|
+
secrets.choice(string.ascii_lowercase + string.digits) for _ in range(4)
|
|
240
|
+
)
|
|
241
|
+
return f"in/{handle}-{suffix}"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _random_palette(seed: int | None = None, size: int = 6) -> dict[str, str]:
|
|
245
|
+
"""Generate a random HCL-inspired color palette.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
seed: Optional deterministic seed for reproducibility.
|
|
249
|
+
size: Number of colors in the palette.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
A dictionary of color key-value pairs.
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
colors = generate_hcl_palette(size, seed=seed or secrets.randbelow(9999) + 1)
|
|
256
|
+
keys = [
|
|
257
|
+
"theme_color",
|
|
258
|
+
"sidebar_color",
|
|
259
|
+
"sidebar_text_color",
|
|
260
|
+
"bar_background_color",
|
|
261
|
+
"date2_color",
|
|
262
|
+
"frame_color",
|
|
263
|
+
]
|
|
264
|
+
mapping = {}
|
|
265
|
+
for key, color in zip(keys, colors):
|
|
266
|
+
mapping[key] = color
|
|
267
|
+
if "frame_color" in mapping and "bold_color" not in mapping:
|
|
268
|
+
mapping["bold_color"] = derive_bold_color(mapping["frame_color"])
|
|
269
|
+
return mapping
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _random_registry_palette() -> dict[str, str] | None:
|
|
273
|
+
"""Select a random palette from the registered palettes.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
A dictionary of color key-value pairs, or None if no palettes are registered.
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
registry = get_palette_registry()
|
|
280
|
+
palettes = registry.list()
|
|
281
|
+
if not palettes:
|
|
282
|
+
return None
|
|
283
|
+
palette = secrets.choice(palettes)
|
|
284
|
+
keys = [
|
|
285
|
+
"theme_color",
|
|
286
|
+
"sidebar_color",
|
|
287
|
+
"sidebar_text_color",
|
|
288
|
+
"bar_background_color",
|
|
289
|
+
"date2_color",
|
|
290
|
+
"frame_color",
|
|
291
|
+
]
|
|
292
|
+
mapping = {}
|
|
293
|
+
for key, color in zip(keys, palette.swatches):
|
|
294
|
+
mapping[key] = color
|
|
295
|
+
mapping["color_scheme"] = palette.name
|
|
296
|
+
|
|
297
|
+
# If palette doesn't have enough swatches, generate missing colors
|
|
298
|
+
if len(palette.swatches) < len(keys):
|
|
299
|
+
missing_keys = keys[len(palette.swatches) :]
|
|
300
|
+
additional_palette = generate_hcl_palette(
|
|
301
|
+
len(missing_keys), seed=secrets.randbelow(9999) + 1
|
|
302
|
+
)
|
|
303
|
+
for key, color in zip(missing_keys, additional_palette):
|
|
304
|
+
mapping[key] = color
|
|
305
|
+
|
|
306
|
+
if "frame_color" in mapping and "bold_color" not in mapping:
|
|
307
|
+
mapping["bold_color"] = derive_bold_color(mapping["frame_color"])
|
|
308
|
+
return mapping
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def generate_random_yaml(
|
|
312
|
+
*,
|
|
313
|
+
output_path: Path,
|
|
314
|
+
template_path: Path,
|
|
315
|
+
seed: int | None = None,
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Generate random resume YAML with palette variations and realistic content."""
|
|
318
|
+
if seed is not None:
|
|
319
|
+
random.seed(seed)
|
|
320
|
+
secrets.SystemRandom(seed)
|
|
321
|
+
|
|
322
|
+
base = safe_load(template_path.read_text(encoding="utf-8"))
|
|
323
|
+
|
|
324
|
+
# Generate realistic personal info
|
|
325
|
+
name = _random_name()
|
|
326
|
+
base["full_name"] = name
|
|
327
|
+
base["job_title"] = secrets.choice(
|
|
328
|
+
[
|
|
329
|
+
"Senior Software Engineer",
|
|
330
|
+
"Principal Engineer",
|
|
331
|
+
"Staff Engineer",
|
|
332
|
+
"Engineering Manager",
|
|
333
|
+
"Technical Lead",
|
|
334
|
+
"Solutions Architect",
|
|
335
|
+
"DevOps Engineer",
|
|
336
|
+
"Full Stack Developer",
|
|
337
|
+
]
|
|
338
|
+
)
|
|
339
|
+
base["phone"] = f"(512) 555-{secrets.randbelow(9000) + 1000}"
|
|
340
|
+
base["email"] = _random_email(name)
|
|
341
|
+
base["linkedin"] = _random_linkedin(name)
|
|
342
|
+
github_handle = name.lower().replace(" ", "")
|
|
343
|
+
base["github"] = github_handle
|
|
344
|
+
base["web"] = f"https://{github_handle}.dev"
|
|
345
|
+
|
|
346
|
+
# Generate realistic summary description
|
|
347
|
+
base["description"] = _random_description()
|
|
348
|
+
|
|
349
|
+
# Generate realistic work experience and project descriptions
|
|
350
|
+
for section_name, section in base["body"].items():
|
|
351
|
+
for entry in section:
|
|
352
|
+
# Determine context based on section name
|
|
353
|
+
if "experience" in section_name.lower() or "work" in section_name.lower():
|
|
354
|
+
context = "accomplishment"
|
|
355
|
+
elif "project" in section_name.lower():
|
|
356
|
+
context = "project"
|
|
357
|
+
else:
|
|
358
|
+
context = "general"
|
|
359
|
+
|
|
360
|
+
# Generate 2-4 bullet points with realistic content
|
|
361
|
+
num_bullets = secrets.randbelow(3) + 2
|
|
362
|
+
bullets = [_random_sentence(context) for _ in range(num_bullets)]
|
|
363
|
+
entry["description"] = "- " + "\n- ".join(bullets)
|
|
364
|
+
|
|
365
|
+
# Add realistic company names for work experience
|
|
366
|
+
if "experience" in section_name.lower() and "company" in entry:
|
|
367
|
+
entry["company"] = secrets.choice(COMPANIES)
|
|
368
|
+
|
|
369
|
+
config = base.setdefault("config", {})
|
|
370
|
+
config["output_mode"] = "markdown"
|
|
371
|
+
config["sidebar_width"] = 60
|
|
372
|
+
config["sidebar_padding_top"] = 6
|
|
373
|
+
config["h3_padding_top"] = 5
|
|
374
|
+
|
|
375
|
+
# Use palette from registry or generate one
|
|
376
|
+
palette = _random_registry_palette()
|
|
377
|
+
if palette is None:
|
|
378
|
+
palette = _random_palette(size=6)
|
|
379
|
+
palette["color_scheme"] = f"generator_{secrets.randbelow(9000) + 1000}"
|
|
380
|
+
config.update(palette)
|
|
381
|
+
if "bold_color" not in config and "frame_color" in config:
|
|
382
|
+
config["bold_color"] = derive_bold_color(config["frame_color"])
|
|
383
|
+
|
|
384
|
+
output_path.write_text(safe_dump(base, sort_keys=False), encoding="utf-8")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def main() -> None:
|
|
388
|
+
"""Run the random palette demo generator CLI."""
|
|
389
|
+
parser = argparse.ArgumentParser(
|
|
390
|
+
description="Generate random resume content + palette demo YAML"
|
|
391
|
+
)
|
|
392
|
+
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
|
393
|
+
parser.add_argument("--template", type=Path, default=DEFAULT_TEMPLATE)
|
|
394
|
+
parser.add_argument(
|
|
395
|
+
"--seed", type=int, help="Deterministic seed for reproducibility"
|
|
396
|
+
)
|
|
397
|
+
args = parser.parse_args()
|
|
398
|
+
|
|
399
|
+
generate_random_yaml(
|
|
400
|
+
output_path=args.output,
|
|
401
|
+
template_path=args.template,
|
|
402
|
+
seed=args.seed,
|
|
403
|
+
)
|
|
404
|
+
print(f"Wrote {args.output}")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Provide configuration helpers and shared constants.
|
|
3
|
+
|
|
4
|
+
See `wiki/Path-Handling-Guide.md` for path handling conventions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import atexit
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from contextlib import ExitStack
|
|
13
|
+
from importlib import resources
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
from simple_resume.core.paths import Paths
|
|
18
|
+
|
|
19
|
+
# Keep an open handle to package resources so they're available even when the
|
|
20
|
+
# distribution is zipped (e.g., installed from a wheel).
|
|
21
|
+
_asset_stack = ExitStack()
|
|
22
|
+
PACKAGE_ROOT = _asset_stack.enter_context(
|
|
23
|
+
resources.as_file(resources.files("simple_resume"))
|
|
24
|
+
)
|
|
25
|
+
ASSETS_ROOT = PACKAGE_ROOT / "shell" / "assets"
|
|
26
|
+
atexit.register(_asset_stack.close)
|
|
27
|
+
|
|
28
|
+
PATH_CONTENT = ASSETS_ROOT
|
|
29
|
+
TEMPLATE_LOC = ASSETS_ROOT / "templates"
|
|
30
|
+
STATIC_LOC = ASSETS_ROOT / "static"
|
|
31
|
+
|
|
32
|
+
# Legacy string-based paths preserved for configuration.
|
|
33
|
+
PATH_DATA = "resume_private"
|
|
34
|
+
PATH_INPUT = f"{PATH_DATA}/input"
|
|
35
|
+
PATH_OUTPUT = f"{PATH_DATA}/output"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if sys.version_info >= (3, 10):
|
|
39
|
+
PathLike = str | os.PathLike[str]
|
|
40
|
+
else:
|
|
41
|
+
PathLike = Union[str, os.PathLike[str]]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def resolve_paths(
|
|
45
|
+
data_dir: PathLike | None = None,
|
|
46
|
+
*,
|
|
47
|
+
content_dir: PathLike | None = None,
|
|
48
|
+
templates_dir: PathLike | None = None,
|
|
49
|
+
static_dir: PathLike | None = None,
|
|
50
|
+
) -> Paths:
|
|
51
|
+
"""Return the active data, input, and output paths.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data_dir: Optional directory containing `input/` and `output/` folders.
|
|
55
|
+
If omitted, the `RESUME_DATA_DIR` environment variable is used.
|
|
56
|
+
If neither is provided, the default is `./resume_private`.
|
|
57
|
+
content_dir: Optional override for the package content directory.
|
|
58
|
+
templates_dir: Optional override for the templates directory.
|
|
59
|
+
static_dir: Optional override for the static assets directory.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A `Paths` dataclass with resolved data, template, and static paths.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
base = data_dir or os.environ.get("RESUME_DATA_DIR") or PATH_DATA
|
|
66
|
+
base_path = Path(base)
|
|
67
|
+
|
|
68
|
+
if data_dir is None:
|
|
69
|
+
data_path = Path(PATH_DATA)
|
|
70
|
+
input_path = Path(PATH_INPUT)
|
|
71
|
+
output_path = Path(PATH_OUTPUT)
|
|
72
|
+
else:
|
|
73
|
+
data_path = base_path
|
|
74
|
+
input_path = base_path / "input"
|
|
75
|
+
output_path = base_path / "output"
|
|
76
|
+
|
|
77
|
+
content_path = Path(content_dir) if content_dir is not None else PATH_CONTENT
|
|
78
|
+
templates_path = (
|
|
79
|
+
Path(templates_dir) if templates_dir is not None else content_path / "templates"
|
|
80
|
+
)
|
|
81
|
+
static_path = (
|
|
82
|
+
Path(static_dir) if static_dir is not None else content_path / "static"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return Paths(
|
|
86
|
+
data=data_path,
|
|
87
|
+
input=input_path,
|
|
88
|
+
output=output_path,
|
|
89
|
+
content=content_path,
|
|
90
|
+
templates=templates_path,
|
|
91
|
+
static=static_path,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Files
|
|
96
|
+
FILE_DEFAULT = "sample_1"
|