codeurcv 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {codeurcv-0.4.0 → codeurcv-0.5.0}/PKG-INFO +5 -2
- {codeurcv-0.4.0 → codeurcv-0.5.0}/README.md +4 -2
- {codeurcv-0.4.0 → codeurcv-0.5.0}/pyproject.toml +1 -1
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/cli.py +1 -1
- codeurcv-0.5.0/src/codeurcv/core/config_loader.py +17 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/renderer.py +2 -2
- codeurcv-0.5.0/src/codeurcv/core/schema.py +78 -0
- codeurcv-0.5.0/src/codeurcv/plugins/minimalist/plugin.py +9 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/plugins/minimalist/template.tex +5 -5
- codeurcv-0.4.0/src/codeurcv/core/schema.py +0 -56
- codeurcv-0.4.0/src/codeurcv/plugins/minimalist/plugin.py +0 -23
- {codeurcv-0.4.0 → codeurcv-0.5.0}/LICENSE +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/__init__.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/__main__.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/constants.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/dependency_checker.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/logger.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/markdown_converter.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/plugin_loader.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/settings.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/core/template_loader.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/plugins/__init__.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/plugins/base.py +0 -0
- {codeurcv-0.4.0 → codeurcv-0.5.0}/src/codeurcv/plugins/minimalist/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeurcv
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A Python library to generate LaTeX resumes from YAML configuration files using Jinja2 templates.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -168,8 +168,10 @@ Automate resume generation on every push using the official GitHub Action.
|
|
|
168
168
|
```yaml
|
|
169
169
|
- uses: crackedngineer/codeurcv-action@v1
|
|
170
170
|
with:
|
|
171
|
-
|
|
171
|
+
config-path: config.yml
|
|
172
172
|
out-dir: output
|
|
173
|
+
template: minimalist
|
|
174
|
+
filename: john-doe-resume
|
|
173
175
|
```
|
|
174
176
|
|
|
175
177
|
→ [codeurcv-action on GitHub](https://github.com/crackedngineer/codeurcv-action) · [View on Marketplace](https://github.com/marketplace/actions/codeurcv-action)
|
|
@@ -185,3 +187,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) — contributions are welcome!
|
|
|
185
187
|
## 📜 License
|
|
186
188
|
|
|
187
189
|
[MIT](LICENSE) © codeurcv contributors
|
|
190
|
+
|
|
@@ -142,8 +142,10 @@ Automate resume generation on every push using the official GitHub Action.
|
|
|
142
142
|
```yaml
|
|
143
143
|
- uses: crackedngineer/codeurcv-action@v1
|
|
144
144
|
with:
|
|
145
|
-
|
|
145
|
+
config-path: config.yml
|
|
146
146
|
out-dir: output
|
|
147
|
+
template: minimalist
|
|
148
|
+
filename: john-doe-resume
|
|
147
149
|
```
|
|
148
150
|
|
|
149
151
|
→ [codeurcv-action on GitHub](https://github.com/crackedngineer/codeurcv-action) · [View on Marketplace](https://github.com/marketplace/actions/codeurcv-action)
|
|
@@ -158,4 +160,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) — contributions are welcome!
|
|
|
158
160
|
|
|
159
161
|
## 📜 License
|
|
160
162
|
|
|
161
|
-
[MIT](LICENSE) © codeurcv contributors
|
|
163
|
+
[MIT](LICENSE) © codeurcv contributors
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codeurcv"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A Python library to generate LaTeX resumes from YAML configuration files using Jinja2 templates."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "crackedngineer", email = "subhomoyrchoudhury@gmail.com" },
|
|
@@ -58,7 +58,7 @@ def run(
|
|
|
58
58
|
|
|
59
59
|
renderer = ResumeRenderer()
|
|
60
60
|
|
|
61
|
-
renderer.render(config_path=input,
|
|
61
|
+
renderer.render(config_path=input, output_dir=output, out_filename=name, template=template)
|
|
62
62
|
|
|
63
63
|
console.print("[bold green]Done![/bold green]")
|
|
64
64
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
LOADERS = {
|
|
6
|
+
".yml": yaml.safe_load,
|
|
7
|
+
".yaml": yaml.safe_load,
|
|
8
|
+
".json": json.loads,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def load_config(path: Path) -> dict:
|
|
12
|
+
loader = LOADERS.get(path.suffix.lower())
|
|
13
|
+
if loader is None:
|
|
14
|
+
raise ValueError(
|
|
15
|
+
f"Unsupported file type '{path.suffix}'. Use .yml, .yaml, or .json"
|
|
16
|
+
)
|
|
17
|
+
return loader(path.read_text())
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import tempfile
|
|
2
2
|
import shutil
|
|
3
|
-
import yaml
|
|
4
3
|
import logging
|
|
5
4
|
import subprocess
|
|
6
5
|
from pathlib import Path
|
|
@@ -13,6 +12,7 @@ from codeurcv.core.schema import ResumeConfig
|
|
|
13
12
|
from codeurcv.core.settings import console
|
|
14
13
|
from codeurcv.core.dependency_checker import check_dependencies
|
|
15
14
|
from codeurcv.core.constants import DEFAULT_OUTPUT_FILENAME
|
|
15
|
+
from codeurcv.core.config_loader import load_config
|
|
16
16
|
|
|
17
17
|
class ResumeRenderer:
|
|
18
18
|
def __init__(self):
|
|
@@ -31,7 +31,7 @@ class ResumeRenderer:
|
|
|
31
31
|
# STEP 1
|
|
32
32
|
raw_data = self._step(
|
|
33
33
|
"Configuration loaded",
|
|
34
|
-
lambda:
|
|
34
|
+
lambda: load_config(config_path),
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
# STEP 2
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from pydantic import BaseModel, Field, GetCoreSchemaHandler
|
|
3
|
+
from pydantic_core import core_schema
|
|
4
|
+
from codeurcv.core.markdown_converter import MarkdownConverter
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
converter = MarkdownConverter()
|
|
8
|
+
class ResumeSection(BaseModel):
|
|
9
|
+
"""Base model for resume sections with optional ordering."""
|
|
10
|
+
priority: Optional[int] = None
|
|
11
|
+
|
|
12
|
+
class MarkdownContent(str):
|
|
13
|
+
"""A string type that will be rendered as bold markdown content."""
|
|
14
|
+
def __new__(cls, content):
|
|
15
|
+
converted_content = converter.convert(content)
|
|
16
|
+
return super().__new__(cls, converted_content)
|
|
17
|
+
|
|
18
|
+
def __init__(self, content):
|
|
19
|
+
try:
|
|
20
|
+
super().__init__()
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logging.error(f"Error initializing MarkdownContent: {e}")
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def __get_pydantic_core_schema__(
|
|
26
|
+
cls, source_type: any, handler: GetCoreSchemaHandler
|
|
27
|
+
) -> core_schema.CoreSchema:
|
|
28
|
+
return core_schema.no_info_plain_validator_function(
|
|
29
|
+
cls,
|
|
30
|
+
serialization=core_schema.plain_serializer_function_ser_schema(str),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
class BasicDetails(BaseModel):
|
|
34
|
+
name: str
|
|
35
|
+
email: str
|
|
36
|
+
phone: Optional[str] = None
|
|
37
|
+
website: Optional[str] = None
|
|
38
|
+
github: Optional[str] = None
|
|
39
|
+
linkedin: Optional[str] = None
|
|
40
|
+
location: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
class Education(ResumeSection):
|
|
43
|
+
institution: str
|
|
44
|
+
location: str
|
|
45
|
+
degree: str
|
|
46
|
+
year: int
|
|
47
|
+
gpa: Optional[str] = None
|
|
48
|
+
additional_information: List[MarkdownContent] = Field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
class Job(ResumeSection):
|
|
51
|
+
company: str
|
|
52
|
+
role: str
|
|
53
|
+
start: str
|
|
54
|
+
end: Optional[str] = "Present"
|
|
55
|
+
location: Optional[str] = None
|
|
56
|
+
achievements: List[MarkdownContent] = Field(default_factory=list)
|
|
57
|
+
technologies: List[MarkdownContent] = Field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
class Project(ResumeSection):
|
|
60
|
+
name: str
|
|
61
|
+
description: List[MarkdownContent] = Field(default_factory=list)
|
|
62
|
+
start: str
|
|
63
|
+
end: Optional[str] = None
|
|
64
|
+
technologies: List[MarkdownContent] = Field(default_factory=list)
|
|
65
|
+
link: Optional[str] = None
|
|
66
|
+
|
|
67
|
+
class Skill(ResumeSection):
|
|
68
|
+
category: str
|
|
69
|
+
featured: List[MarkdownContent] = Field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
class ResumeConfig(BaseModel):
|
|
72
|
+
basic_details: BasicDetails
|
|
73
|
+
summary: Optional[MarkdownContent] = None
|
|
74
|
+
education: List[Education] = Field(default_factory=list)
|
|
75
|
+
work: List[Job] = Field(default_factory=list)
|
|
76
|
+
projects: List[Project] = Field(default_factory=list)
|
|
77
|
+
skills: List[Skill] = Field(default_factory=list)
|
|
78
|
+
location: Optional[str] = None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from codeurcv.plugins.base import TemplatePlugin
|
|
3
|
+
|
|
4
|
+
class MinimalistTemplate(TemplatePlugin):
|
|
5
|
+
name = "minimalist"
|
|
6
|
+
description = "Clean minimalist resume style"
|
|
7
|
+
|
|
8
|
+
def template_path(self) -> Path:
|
|
9
|
+
return Path(__file__).parent / "template.tex"
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
((* for school in education *))
|
|
129
129
|
\resumeSubheading
|
|
130
130
|
{ ((( school.institution ))) }{ ((( school.location ))) }
|
|
131
|
-
{ ((( school.degree ))); GPA: ((( school.gpa ))) }{ ((( school.
|
|
131
|
+
{ ((( school.degree ))); GPA: ((( school.gpa ))) }{ ((( school.year | string ))) }
|
|
132
132
|
((* endfor *))
|
|
133
133
|
\resumeSubHeadingListEnd
|
|
134
134
|
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
((* for job in work *))
|
|
139
139
|
\resumeSubheading
|
|
140
140
|
{ ((( job.company ))) }{ ((( job.location ))) }
|
|
141
|
-
{ ((( job.role ))) }{ ((( job.
|
|
141
|
+
{ ((( job.role ))) }{ ((( job.start ))) - ((( job.end ))) }
|
|
142
142
|
\resumeItemListStart
|
|
143
143
|
((* for item in job.achievements *))
|
|
144
144
|
\resumeItem{ ((( item | safe ))) }
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
((* for proj in projects *))
|
|
156
156
|
\resumeProjectHeading
|
|
157
157
|
{\href{ ((( proj.link ))) }{ \textbf{((( proj.name )))}}((* if proj.technologies *)) $|$ \emph{((( proj.technologies | join(', ') )))}((* endif *))}
|
|
158
|
-
{((( proj.
|
|
158
|
+
{((( proj.start ))) - ((( proj.end )))}
|
|
159
159
|
\resumeItemListStart
|
|
160
160
|
((* for item in proj.description *))
|
|
161
161
|
\resumeItem{((( item | safe )))}
|
|
@@ -167,10 +167,10 @@
|
|
|
167
167
|
%-----------TECHNICAL SKILLS-----------------
|
|
168
168
|
\section{Technical Skills}
|
|
169
169
|
\resumeSubHeadingListStart
|
|
170
|
-
((* for
|
|
170
|
+
((* for skill in skills *))
|
|
171
171
|
\item\small
|
|
172
172
|
\begin{tabular*}{0.97\textwidth}{l@{\extracolsep{\fill}}r}
|
|
173
|
-
\textbf{ ((( category
|
|
173
|
+
\textbf{ ((( skill.category|capitalize )))}: {((( skill.featured | join(", ") )))} \\
|
|
174
174
|
\end{tabular*}\vspace{-7pt}
|
|
175
175
|
((* endfor *))
|
|
176
176
|
\resumeSubHeadingListEnd
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
|
-
from pydantic import BaseModel, Field, field_validator
|
|
3
|
-
|
|
4
|
-
from codeurcv.core.constants import DEFAULT_TEMPLATE
|
|
5
|
-
|
|
6
|
-
class ResumeSection(BaseModel):
|
|
7
|
-
"""Base model for resume sections with optional ordering."""
|
|
8
|
-
priority: Optional[int] = None
|
|
9
|
-
|
|
10
|
-
class BasicDetails(BaseModel):
|
|
11
|
-
name: str
|
|
12
|
-
email: str
|
|
13
|
-
phone: Optional[str] = None
|
|
14
|
-
website: Optional[str] = None
|
|
15
|
-
github: Optional[str] = None
|
|
16
|
-
linkedin: Optional[str] = None
|
|
17
|
-
location: Optional[str] = None
|
|
18
|
-
|
|
19
|
-
class Education(ResumeSection):
|
|
20
|
-
institution: str
|
|
21
|
-
location: str
|
|
22
|
-
degree: str
|
|
23
|
-
duration: str
|
|
24
|
-
gpa: Optional[str] = None
|
|
25
|
-
additional_information: List[str] = Field(default_factory=list)
|
|
26
|
-
|
|
27
|
-
class Job(ResumeSection):
|
|
28
|
-
company: str
|
|
29
|
-
role: str
|
|
30
|
-
duration: str
|
|
31
|
-
achievements: List[str] = Field(default_factory=list)
|
|
32
|
-
technologies: List[str] = Field(default_factory=list)
|
|
33
|
-
|
|
34
|
-
class Project(ResumeSection):
|
|
35
|
-
name: str
|
|
36
|
-
description: List[str] = Field(default_factory=list)
|
|
37
|
-
technologies: List[str] = Field(default_factory=list)
|
|
38
|
-
link: Optional[str] = None
|
|
39
|
-
date: Optional[str] = None
|
|
40
|
-
|
|
41
|
-
class Skill(ResumeSection):
|
|
42
|
-
name: str
|
|
43
|
-
featured_skills: List[str] = Field(default_factory=list)
|
|
44
|
-
|
|
45
|
-
class ResumeConfig(BaseModel):
|
|
46
|
-
basic_details: BasicDetails
|
|
47
|
-
summary: Optional[str] = None
|
|
48
|
-
education: List[Education] = Field(default_factory=list)
|
|
49
|
-
work: List[Job] = Field(default_factory=list)
|
|
50
|
-
projects: List[Project] = Field(default_factory=list)
|
|
51
|
-
skills: List[Skill] = Field(default_factory=list)
|
|
52
|
-
|
|
53
|
-
@field_validator("filename")
|
|
54
|
-
@classmethod
|
|
55
|
-
def strip_pdf_extension(cls, v: str) -> str:
|
|
56
|
-
return v.removesuffix(".pdf")
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from codeurcv.core.schema import ResumeConfig
|
|
4
|
-
from codeurcv.plugins.base import TemplatePlugin
|
|
5
|
-
from codeurcv.core.markdown_converter import MarkdownConverter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MinimalistTemplate(TemplatePlugin):
|
|
9
|
-
name = "minimalist"
|
|
10
|
-
description = "Clean minimalist resume style"
|
|
11
|
-
|
|
12
|
-
def template_path(self) -> Path:
|
|
13
|
-
return Path(__file__).parent / "template.tex"
|
|
14
|
-
|
|
15
|
-
def preprocess(self, data: ResumeConfig) -> ResumeConfig:
|
|
16
|
-
converter = MarkdownConverter()
|
|
17
|
-
if data.summary:
|
|
18
|
-
data.summary = converter.convert(data.summary)
|
|
19
|
-
for job in data.work:
|
|
20
|
-
job.achievements = [converter.convert(achievement) for achievement in job.achievements]
|
|
21
|
-
for proj in data.projects:
|
|
22
|
-
proj.description = [converter.convert(desc) for desc in proj.description]
|
|
23
|
-
return data
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|