mirror of https://github.com/eyhc1/rendercv.git
refactor
This commit is contained in:
parent
9cdd7e5b0f
commit
4178c3e5e9
|
@ -54,7 +54,7 @@ def welcome():
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
|
||||||
def warning(text):
|
def warning(text: str):
|
||||||
"""Print a warning message to the terminal.
|
"""Print a warning message to the terminal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -63,7 +63,7 @@ def warning(text):
|
||||||
print(f"[bold yellow]{text}")
|
print(f"[bold yellow]{text}")
|
||||||
|
|
||||||
|
|
||||||
def error(text, exception=None):
|
def error(text: str, exception: Optional[Exception] = None):
|
||||||
"""Print an error message to the terminal.
|
"""Print an error message to the terminal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -80,7 +80,7 @@ def error(text, exception=None):
|
||||||
print(f"\n[bold red]{text}\n")
|
print(f"\n[bold red]{text}\n")
|
||||||
|
|
||||||
|
|
||||||
def information(text):
|
def information(text: str):
|
||||||
"""Print an information message to the terminal.
|
"""Print an information message to the terminal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
@ -53,7 +53,7 @@ def get_date_object(date: str | int) -> Date:
|
||||||
data models.
|
data models.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date_string (str): The date string to parse.
|
date (str): The date string to parse.
|
||||||
Returns:
|
Returns:
|
||||||
datetime.date: The parsed date.
|
datetime.date: The parsed date.
|
||||||
"""
|
"""
|
||||||
|
@ -528,7 +528,7 @@ class PublicationEntry(RenderCVBaseModel):
|
||||||
"""Check if the DOI exists in the DOI System."""
|
"""Check if the DOI exists in the DOI System."""
|
||||||
# see https://stackoverflow.com/a/60671292/18840665 for the explanation of the
|
# see https://stackoverflow.com/a/60671292/18840665 for the explanation of the
|
||||||
# next line:
|
# next line:
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
ssl._create_default_https_context = ssl._create_unverified_context # type: ignore
|
||||||
|
|
||||||
doi_url = f"http://doi.org/{doi}"
|
doi_url = f"http://doi.org/{doi}"
|
||||||
|
|
||||||
|
@ -550,7 +550,8 @@ class PublicationEntry(RenderCVBaseModel):
|
||||||
"""Return the date string of the publication."""
|
"""Return the date string of the publication."""
|
||||||
if isinstance(self.date, int):
|
if isinstance(self.date, int):
|
||||||
date_string = str(self.date)
|
date_string = str(self.date)
|
||||||
elif isinstance(self.date, str):
|
else:
|
||||||
|
# Then it is a string
|
||||||
date_object = get_date_object(self.date)
|
date_object = get_date_object(self.date)
|
||||||
date_string = format_date(date_object)
|
date_string = format_date(date_object)
|
||||||
|
|
||||||
|
@ -667,13 +668,9 @@ def get_entry_and_section_type(
|
||||||
"""Determine the entry and section type based on the entry.
|
"""Determine the entry and section type based on the entry.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry (dict[str, Any] | EducationEntry | ExperienceEntry | PublicationEntry |
|
entry (dict[str, Any] | EducationEntry | ExperienceEntry | PublicationEntry | NormalEntry | OneLineEntry | str): The entry to determine the type.
|
||||||
NormalEntry | OneLineEntry | str): The entry to determine the type.
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[str, Type[SectionWithTextEntries | SectionWithOneLineEntries |
|
tuple[str, Type[SectionWithTextEntries | SectionWithOneLineEntries | SectionWithExperienceEntries | SectionWithEducationEntries | SectionWithPublicationEntries | SectionWithNormalEntries]]: The entry type and the section type.
|
||||||
SectionWithExperienceEntries | SectionWithEducationEntries |
|
|
||||||
SectionWithPublicationEntries | SectionWithNormalEntries]]: The entry type and the
|
|
||||||
section type.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(entry, dict):
|
if isinstance(entry, dict):
|
||||||
if "details" in entry:
|
if "details" in entry:
|
||||||
|
@ -709,7 +706,7 @@ def get_entry_and_section_type(
|
||||||
elif isinstance(entry, PublicationEntry):
|
elif isinstance(entry, PublicationEntry):
|
||||||
entry_type = "PublicationEntry"
|
entry_type = "PublicationEntry"
|
||||||
section_type = SectionWithPublicationEntries
|
section_type = SectionWithPublicationEntries
|
||||||
elif isinstance(entry, NormalEntry):
|
elif isinstance(entry, NormalEntry): # type: ignore
|
||||||
entry_type = "NormalEntry"
|
entry_type = "NormalEntry"
|
||||||
section_type = SectionWithNormalEntries
|
section_type = SectionWithNormalEntries
|
||||||
else:
|
else:
|
||||||
|
@ -892,7 +889,7 @@ class CurriculumVitae(RenderCVBaseModel):
|
||||||
title="Social Networks",
|
title="Social Networks",
|
||||||
description="The social networks of the person.",
|
description="The social networks of the person.",
|
||||||
)
|
)
|
||||||
sections_input: dict[str, SectionInput] = pydantic.Field(
|
sections_input: Optional[dict[str, SectionInput]] = pydantic.Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Sections",
|
title="Sections",
|
||||||
description="The sections of the CV.",
|
description="The sections of the CV.",
|
||||||
|
@ -902,11 +899,11 @@ class CurriculumVitae(RenderCVBaseModel):
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def sections(self) -> list[Section]:
|
def sections(self) -> list[Section]:
|
||||||
"""Return all the sections of the CV with their titles."""
|
"""Return all the sections of the CV with their titles."""
|
||||||
sections = []
|
sections: list[Section] = []
|
||||||
if self.sections_input is not None:
|
if self.sections_input is not None:
|
||||||
for title, section_or_entries in self.sections_input.items():
|
for title, section_or_entries in self.sections_input.items():
|
||||||
title = title.replace("_", " ").title()
|
title = title.replace("_", " ").title()
|
||||||
if isinstance(section_or_entries, list):
|
|
||||||
entry_type, section_type = get_entry_and_section_type(
|
entry_type, section_type = get_entry_and_section_type(
|
||||||
section_or_entries[0]
|
section_or_entries[0]
|
||||||
)
|
)
|
||||||
|
@ -918,12 +915,6 @@ class CurriculumVitae(RenderCVBaseModel):
|
||||||
)
|
)
|
||||||
sections.append(section)
|
sections.append(section)
|
||||||
|
|
||||||
else:
|
|
||||||
raise RuntimeError(
|
|
||||||
"This error shouldn't have been raised. Please open an"
|
|
||||||
" issue on GitHub."
|
|
||||||
)
|
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
|
@ -981,6 +972,12 @@ class RenderCVDataModel(RenderCVBaseModel):
|
||||||
return rendercv_design_validator.validate_python(design)
|
return rendercv_design_validator.validate_python(design)
|
||||||
else:
|
else:
|
||||||
theme_name: str = design["theme"] # type: ignore
|
theme_name: str = design["theme"] # type: ignore
|
||||||
|
if not isinstance(theme_name, str):
|
||||||
|
raise RuntimeError(
|
||||||
|
"This error shouldn't have been raised. Please open an issue on"
|
||||||
|
" GitHub."
|
||||||
|
)
|
||||||
|
|
||||||
# check if the theme name is valid:
|
# check if the theme name is valid:
|
||||||
if not theme_name.isalpha():
|
if not theme_name.isalpha():
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -1093,7 +1090,7 @@ def read_input_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
file_content = file_path.read_text(encoding="utf-8")
|
file_content = file_path.read_text(encoding="utf-8")
|
||||||
input_as_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content)
|
input_as_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content) # type: ignore
|
||||||
|
|
||||||
# validate the parsed dictionary by creating an instance of RenderCVDataModel:
|
# validate the parsed dictionary by creating an instance of RenderCVDataModel:
|
||||||
rendercv_data_model = RenderCVDataModel(**input_as_dictionary)
|
rendercv_data_model = RenderCVDataModel(**input_as_dictionary)
|
||||||
|
@ -1224,7 +1221,7 @@ def get_a_sample_data_model(name: str = "John Doe") -> RenderCVDataModel:
|
||||||
SocialNetwork(network="LinkedIn", username="yourusername"),
|
SocialNetwork(network="LinkedIn", username="yourusername"),
|
||||||
SocialNetwork(network="GitHub", username="yourusername"),
|
SocialNetwork(network="GitHub", username="yourusername"),
|
||||||
],
|
],
|
||||||
sections=sections,
|
sections=sections, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
design = ClassicThemeOptions(theme="classic", show_timespan_in=["Experience"])
|
design = ClassicThemeOptions(theme="classic", show_timespan_in=["Experience"])
|
||||||
|
@ -1232,7 +1229,7 @@ def get_a_sample_data_model(name: str = "John Doe") -> RenderCVDataModel:
|
||||||
return RenderCVDataModel(cv=cv, design=design)
|
return RenderCVDataModel(cv=cv, design=design)
|
||||||
|
|
||||||
|
|
||||||
def generate_json_schema() -> dict:
|
def generate_json_schema() -> dict[str, Any]:
|
||||||
"""Generate the JSON schema of RenderCV.
|
"""Generate the JSON schema of RenderCV.
|
||||||
|
|
||||||
JSON schema is generated for the users to make it easier for them to write the input
|
JSON schema is generated for the users to make it easier for them to write the input
|
||||||
|
@ -1245,7 +1242,7 @@ def generate_json_schema() -> dict:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema):
|
class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema):
|
||||||
def generate(self, schema, mode="validation"):
|
def generate(self, schema, mode="validation"): # type: ignore
|
||||||
json_schema = super().generate(schema, mode=mode)
|
json_schema = super().generate(schema, mode=mode)
|
||||||
|
|
||||||
# Basic information about the schema:
|
# Basic information about the schema:
|
||||||
|
@ -1258,7 +1255,7 @@ def generate_json_schema() -> dict:
|
||||||
|
|
||||||
# Loop through $defs and remove docstring descriptions and fix optional
|
# Loop through $defs and remove docstring descriptions and fix optional
|
||||||
# fields
|
# fields
|
||||||
for key, value in json_schema["$defs"].items():
|
for _, value in json_schema["$defs"].items():
|
||||||
# Don't allow additional properties
|
# Don't allow additional properties
|
||||||
value["additionalProperties"] = False
|
value["additionalProperties"] = False
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class TemplatedFile:
|
||||||
|
|
||||||
def template(
|
def template(
|
||||||
self,
|
self,
|
||||||
theme_name,
|
theme_name: str,
|
||||||
template_name: Literal[
|
template_name: Literal[
|
||||||
"EducationEntry",
|
"EducationEntry",
|
||||||
"ExperienceEntry",
|
"ExperienceEntry",
|
||||||
|
@ -75,14 +75,7 @@ class TemplatedFile:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
template_name (str): The name of the template file.
|
template_name (str): The name of the template file.
|
||||||
entry (Optional[
|
entry (Optional[dm.EducationEntry, dm.ExperienceEntry, dm.NormalEntry,dm.PublicationEntry, dm.OneLineEntry, str]): The data model of the entry.
|
||||||
dm.EducationEntry,
|
|
||||||
dm.ExperienceEntry,
|
|
||||||
dm.NormalEntry,
|
|
||||||
dm.PublicationEntry,
|
|
||||||
dm.OneLineEntry,
|
|
||||||
str
|
|
||||||
]): The data model of the entry.
|
|
||||||
section_title (Optional[str]): The title of the section.
|
section_title (Optional[str]): The title of the section.
|
||||||
is_first_entry (Optional[bool]): Whether the entry is the first one in the
|
is_first_entry (Optional[bool]): Whether the entry is the first one in the
|
||||||
section.
|
section.
|
||||||
|
@ -144,7 +137,7 @@ class LaTeXFile(TemplatedFile):
|
||||||
)
|
)
|
||||||
super().__init__(data_model, environment)
|
super().__init__(data_model, environment)
|
||||||
|
|
||||||
def render_templates(self):
|
def render_templates(self) -> tuple[str, str, list[tuple[str, list[str], str]]]:
|
||||||
"""Render and return all the templates for the $\\LaTeX$ file.
|
"""Render and return all the templates for the $\\LaTeX$ file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -154,12 +147,12 @@ class LaTeXFile(TemplatedFile):
|
||||||
# Template the preamble, header, and sections:
|
# Template the preamble, header, and sections:
|
||||||
preamble = self.template("Preamble")
|
preamble = self.template("Preamble")
|
||||||
header = self.template("Header")
|
header = self.template("Header")
|
||||||
sections = []
|
sections: list[tuple[str, list[str], str]] = []
|
||||||
for section in self.cv.sections:
|
for section in self.cv.sections:
|
||||||
section_beginning = self.template(
|
section_beginning = self.template(
|
||||||
"SectionBeginning", section_title=section.title
|
"SectionBeginning", section_title=section.title
|
||||||
)
|
)
|
||||||
entries = []
|
entries: list[str] = []
|
||||||
for i, entry in enumerate(section.entries):
|
for i, entry in enumerate(section.entries):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
is_first_entry = True
|
is_first_entry = True
|
||||||
|
@ -203,7 +196,17 @@ class LaTeXFile(TemplatedFile):
|
||||||
section_title: Optional[str] = None,
|
section_title: Optional[str] = None,
|
||||||
is_first_entry: Optional[bool] = None,
|
is_first_entry: Optional[bool] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Template one of the files in the `themes` directory."""
|
"""Template one of the files in the `themes` directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_name (str): The name of the template file.
|
||||||
|
entry (Optional[dm.EducationEntry, dm.ExperienceEntry, dm.NormalEntry,dm.PublicationEntry, dm.OneLineEntry, str]): The data model of the entry.
|
||||||
|
section_title (Optional[str]): The title of the section.
|
||||||
|
is_first_entry (Optional[bool]): Whether the entry is the first one in the section.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The templated file.
|
||||||
|
"""
|
||||||
result = super().template(
|
result = super().template(
|
||||||
self.design.theme,
|
self.design.theme,
|
||||||
template_name,
|
template_name,
|
||||||
|
@ -215,7 +218,11 @@ class LaTeXFile(TemplatedFile):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_latex_code(self):
|
def get_latex_code(self):
|
||||||
"""Get the $\\LaTeX$ code of the file."""
|
"""Get the $\\LaTeX$ code of the file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The $\\LaTeX$ code.
|
||||||
|
"""
|
||||||
preamble, header, sections = self.render_templates()
|
preamble, header, sections = self.render_templates()
|
||||||
return self.get_full_code(
|
return self.get_full_code(
|
||||||
"main.j2.tex",
|
"main.j2.tex",
|
||||||
|
@ -234,18 +241,13 @@ class MarkdownFile(TemplatedFile):
|
||||||
data model and Jinja2 templates. It inherits from the TemplatedFile class. Markdown
|
data model and Jinja2 templates. It inherits from the TemplatedFile class. Markdown
|
||||||
files are generated to produce a PDF which can be copy-pasted to
|
files are generated to produce a PDF which can be copy-pasted to
|
||||||
[Grammarly](https://app.grammarly.com/) for proofreading.
|
[Grammarly](https://app.grammarly.com/) for proofreading.
|
||||||
|
|
||||||
Args:
|
|
||||||
data_model (dm.RenderCVDataModel): The data model.
|
|
||||||
environment (jinja2.Environment): The Jinja2 environment.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def render_templates(self):
|
def render_templates(self):
|
||||||
"""Render and return all the templates for the Markdown file.
|
"""Render and return all the templates for the Markdown file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[str, List[Tuple[str, List[str]]]: The header and sections of the
|
Tuple[str, List[Tuple[str, List[str]]]]: The header and sections of the Markdown file.
|
||||||
Markdown file.
|
|
||||||
"""
|
"""
|
||||||
# Template the header and sections:
|
# Template the header and sections:
|
||||||
header = self.template("Header")
|
header = self.template("Header")
|
||||||
|
@ -297,7 +299,17 @@ class MarkdownFile(TemplatedFile):
|
||||||
section_title: Optional[str] = None,
|
section_title: Optional[str] = None,
|
||||||
is_first_entry: Optional[bool] = None,
|
is_first_entry: Optional[bool] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Template one of the files in the `themes` directory."""
|
"""Template one of the files in the `themes` directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_name (str): The name of the template file.
|
||||||
|
entry (Optional[dm.EducationEntry, dm.ExperienceEntry, dm.NormalEntry,dm.PublicationEntry, dm.OneLineEntry, str]): The data model of the entry.
|
||||||
|
section_title (Optional[str]): The title of the section.
|
||||||
|
is_first_entry (Optional[bool]): Whether the entry is the first one in the section.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The templated file.
|
||||||
|
"""
|
||||||
result = super().template(
|
result = super().template(
|
||||||
"markdown",
|
"markdown",
|
||||||
template_name,
|
template_name,
|
||||||
|
@ -729,7 +741,7 @@ def divide_length_by(length: str, divider: float) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_an_item_with_a_specific_attribute_value(
|
def get_an_item_with_a_specific_attribute_value(
|
||||||
items: list[Any], attribute: str, value: Any
|
items: Optional[list[Any]], attribute: str, value: Any
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Get an item from a list of items with a specific attribute value.
|
"""Get an item from a list of items with a specific attribute value.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue