ensure type safety

This commit is contained in:
Sina Atalay committed 2024-07-05 16:10:25 +03:00
1 parent b75139210f
commit dc87688980
16 files changed
+198 -176

No files matched your search

+2 -2
View File
@@ -1,8 +1,8 @@
"""RenderCV package.
RenderCV is a $\LaTeX$ CV/resume framework that allows you to create a high-quality CV
RenderCV is a $\\LaTeX$ CV/resume framework that allows you to create a high-quality CV
as a PDF from a YAML file with Markdown syntax support and complete control over the
$\LaTeX$ code.
$\\LaTeX$ code.
"""
__version__ = "1.11"
+19 -18
View File
@@ -130,27 +130,26 @@ def cli_command_render(
' [cyan bold]--cv.phone "123-456-7890"[/cyan bold].',
),
] = None,
extra_data_model_override_argumets: typer.Context = None,
extra_data_model_override_argumets: Optional[typer.Context] = None,
):
"""Render a CV from a YAML input file."""
printer.welcome()
# Get paths:
input_file_path = utilities.string_to_file_path(input_file_name)
input_file_path: pathlib.Path = utilities.string_to_file_path(
input_file_name
) # type: ignore
output_directory = pathlib.Path.cwd() / output_folder_name
paths: dict[
Literal["latex", "pdf", "markdown", "html", "png"], Optional[pathlib.Path]
] = {
"latex": latex_path,
"pdf": pdf_path,
"markdown": markdown_path,
"html": html_path,
"png": png_path,
"latex": utilities.string_to_file_path(latex_path),
"pdf": utilities.string_to_file_path(pdf_path),
"markdown": utilities.string_to_file_path(markdown_path),
"html": utilities.string_to_file_path(html_path),
"png": utilities.string_to_file_path(png_path),
}
for file_type, path in paths.items():
if path:
paths[file_type] = utilities.string_to_file_path(path)
# change the current working directory to the input file's directory (because
# the template overrides are looked up in the current working directory):
@@ -198,7 +197,7 @@ def cli_command_render(
progress.finish_the_current_step()
progress.start_a_step("Rendering the LaTeX file to a PDF")
pdf_file_path_in_output_folder = renderer.render_pdf_from_latex(
pdf_file_path_in_output_folder = renderer.render_a_pdf_from_latex(
latex_file_path_in_output_folder, use_local_latex_command
)
if paths["pdf"]:
@@ -219,19 +218,21 @@ def cli_command_render(
markdown_file_path_in_output_folder = renderer.render_a_markdown_file(
data_model, output_directory
)
if markdown_path:
utilities.copy_files(markdown_file_path_in_output_folder, markdown_path)
if paths["markdown"]:
utilities.copy_files(
markdown_file_path_in_output_folder, paths["markdown"]
)
progress.finish_the_current_step()
if not dont_generate_html:
progress.start_a_step(
"Rendering the Markdown file to a HTML (for Grammarly)"
)
html_file_path_in_output_folder = renderer.render_html_from_markdown(
html_file_path_in_output_folder = renderer.render_an_html_from_markdown(
markdown_file_path_in_output_folder
)
if html_path:
utilities.copy_files(html_file_path_in_output_folder, html_path)
if paths["html"]:
utilities.copy_files(html_file_path_in_output_folder, paths["html"])
progress.finish_the_current_step()
@@ -289,7 +290,7 @@ def cli_command_new(
created_files_and_folders.append(input_file_path.name)
except ValueError as e:
# if the theme is not in the available themes, then raise an error
printer.error(e)
printer.error(exception=e)
if not dont_create_theme_source_files:
# copy the package's theme files to the current directory
@@ -352,7 +353,7 @@ def cli_command_create_theme(
)
theme_folder = utilities.copy_templates(
based_on, pathlib.Path.cwd(), new_folder_name=theme_name, suppress_warning=True
based_on, pathlib.Path.cwd(), new_folder_name=theme_name
)
if theme_folder is None:
+7 -3
View File
@@ -14,6 +14,10 @@ import ruamel.yaml
import ruamel.yaml.parser
import typer
from rich import print
import rich.table
import rich.progress
import rich.text
import rich.panel
from .. import __version__
from . import utilities
@@ -392,7 +396,7 @@ def handle_and_print_raised_exceptions(function: Callable) -> Callable:
e,
)
except FileNotFoundError as e:
error(e)
error(exception=e)
except UnicodeDecodeError as e:
# find the problematic character that cannot be decoded with utf-8
bad_character = str(e.object[e.start : e.end])
@@ -406,7 +410,7 @@ def handle_and_print_raised_exceptions(function: Callable) -> Callable:
f" UTF-8 ({bad_character}):\n {bad_character_context}",
)
except ValueError as e:
error(e)
error(exception=e)
except typer.Exit:
pass
except jinja2.exceptions.TemplateSyntaxError as e:
@@ -416,6 +420,6 @@ def handle_and_print_raised_exceptions(function: Callable) -> Callable:
e,
)
except RuntimeError as e:
error(e)
error(exception=e)
return wrapper
+22 -13
View File
@@ -12,24 +12,30 @@ from typing import Optional
import pydantic
import typer
from .. import data
def string_to_file_path(string: str) -> pathlib.Path:
"""Convert a string to a pathlib.Path object.
def string_to_file_path(string: Optional[str]) -> Optional[pathlib.Path]:
"""Convert a string to a pathlib.Path object. If the string is None, then return
None.
Args:
string (str): The string to be converted to a pathlib.Path object.
Returns:
pathlib.Path: The pathlib.Path object.
"""
return pathlib.Path(string).absolute()
if string is not None:
return pathlib.Path(string).absolute()
else:
return None
def set_or_update_a_value(
data_model: pydantic.BaseModel | dict | list,
data_model: pydantic.BaseModel,
key: str,
value: str,
sub_model: pydantic.BaseModel | dict | list = None,
):
sub_model: Optional[pydantic.BaseModel | dict | list] = None,
) -> pydantic.BaseModel: # type: ignore
"""Set or update a value in a data model for a specific key. For example, a key can
be `cv.sections.education.3.institution` and the value can be "Bogazici University".
@@ -98,19 +104,19 @@ def set_or_update_a_value(
def set_or_update_values(
data_model: pydantic.BaseModel,
data_model: data.RenderCVDataModel,
key_and_values: dict[str, str],
) -> pydantic.BaseModel:
"""Set or update values in a data model for specific keys. It uses the
) -> data.RenderCVDataModel:
"""Set or update values in `RenderCVDataModel` for specific keys. It uses the
`set_or_update_a_value` function to set or update the values.
Args:
data_model (pydantic.BaseModel): The data model to set or update the values.
data_model (data.RenderCVDataModel): The data model to set or update the values.
key_and_values (dict[str, str]): The key and value pairs to set or update.
"""
for key, value in key_and_values.items():
try:
data_model = set_or_update_a_value(data_model, key_and_values)
data_model = set_or_update_a_value(data_model, key, value) # type: ignore
except pydantic.ValidationError as e:
raise e
except (ValueError, KeyError, IndexError, AttributeError):
@@ -119,7 +125,7 @@ def set_or_update_values(
return data_model
def copy_files(paths: list[pathlib.Path], new_path: pathlib.Path):
def copy_files(paths: list[pathlib.Path] | pathlib.Path, new_path: pathlib.Path):
"""Copy files to the given path. If there are multiple files, then rename the new
path by adding a number to the end of the path.
@@ -127,6 +133,9 @@ def copy_files(paths: list[pathlib.Path], new_path: pathlib.Path):
paths (list[pathlib.Path]): The paths of the files to be copied.
new_path (pathlib.Path): The path to copy the files to.
"""
if isinstance(paths, pathlib.Path):
paths = [paths]
if len(paths) == 1:
shutil.copy2(paths[0], new_path)
else:
@@ -210,7 +219,7 @@ def copy_templates(
Optional[pathlib.Path]: The path to the copied folder.
"""
# copy the package's theme files to the current directory
template_directory = pathlib.Path(__file__).parent / "themes" / folder_name
template_directory = pathlib.Path(__file__).parent.parent / "themes" / folder_name
if new_folder_name:
destination = copy_to / new_folder_name
else:
+2
View File
@@ -34,6 +34,7 @@ from .models import (
available_themes,
format_date,
SectionContents,
available_entry_type_names,
)
from .reader import read_input_file
@@ -59,4 +60,5 @@ __all__ = [
"Entry",
"available_social_networks",
"SectionContents",
"available_entry_type_names",
]
+2
View File
@@ -36,6 +36,7 @@ from .entry_types import (
NormalEntry,
OneLineEntry,
PublicationEntry,
available_entry_type_names,
)
from .locale_catalog import LocaleCatalog
from .rendercv_data_model import RenderCVDataModel
@@ -57,4 +58,5 @@ __all__ = [
"available_social_networks",
"SectionContents",
"available_themes",
"available_entry_type_names",
]
+7 -98
View File
@@ -8,7 +8,6 @@ import re
from datetime import date as Date
from typing import Optional
from .curriculum_vitae import CurriculumVitae
from .locale_catalog import locale_catalog
@@ -47,9 +46,9 @@ def format_date(date: Date, use_full_name: bool = False) -> str:
def compute_time_span_string(
start_date: Optional[str],
end_date: Optional[str],
date: Optional[str],
start_date: Optional[str | int],
end_date: Optional[str | int],
date: Optional[str | int],
) -> str:
"""
Return a time span string based on the provided dates.
@@ -135,9 +134,9 @@ def compute_time_span_string(
def compute_date_string(
start_date: Optional[str],
end_date: Optional[str],
date: Optional[str],
start_date: Optional[str | int],
end_date: Optional[str | int],
date: Optional[str | int],
show_only_years: bool = False,
) -> str:
"""Return a date string based on the provided dates.
@@ -194,7 +193,7 @@ def compute_date_string(
start_date = format_date(date_object)
if end_date == "present":
end_date = locale_catalog["present"]
end_date = locale_catalog["present"] # type: ignore
elif isinstance(end_date, int):
# Then it means only the year is provided
end_date = str(end_date)
@@ -246,96 +245,6 @@ def compute_social_network_url(network: str, username: str):
return url
def compute_connections(cv: "CurriculumVitae") -> list[dict[str, str]]:
"""Bring together all the connections in the CV, such as social networks, phone
number, email, etc and return them as a list of dictionaries. Each dictionary
contains the following keys: "latex_icon", "url", "clean_url", and "placeholder."
The connections are used in the header of the CV.
Args:
cv (CurriculumVitae): The CV to compute the connections.
Returns:
list[dict[str, str]]: The computed connections.
"""
connections: list[dict[str, str]] = []
if cv.location is not None:
connections.append(
{
"latex_icon": "\\faMapMarker*",
"url": None,
"clean_url": None,
"placeholder": cv.location,
}
)
if cv.email is not None:
connections.append(
{
"latex_icon": "\\faEnvelope[regular]",
"url": f"mailto:{cv.email}",
"clean_url": cv.email,
"placeholder": cv.email,
}
)
if cv.phone is not None:
phone_placeholder = cv.phone.replace("tel:", "").replace("-", " ")
connections.append(
{
"latex_icon": "\\faPhone*",
"url": f"{cv.phone}",
"clean_url": phone_placeholder,
"placeholder": phone_placeholder,
}
)
if cv.website is not None:
website_placeholder = make_a_url_clean(cv.website)
connections.append(
{
"latex_icon": "\\faLink",
"url": cv.website,
"clean_url": website_placeholder,
"placeholder": website_placeholder,
}
)
if cv.social_networks is not None:
icon_dictionary = {
"LinkedIn": "\\faLinkedinIn",
"GitHub": "\\faGithub",
"GitLab": "\\faGitlab",
"Instagram": "\\faInstagram",
"Mastodon": "\\faMastodon",
"ORCID": "\\faOrcid",
"StackOverflow": "\\faStackOverflow",
"ResearchGate": "\\faResearchgate",
"YouTube": "\\faYoutube",
"Google Scholar": "\\faGraduationCap",
}
for social_network in cv.social_networks:
clean_url = make_a_url_clean(social_network.url)
connection = {
"latex_icon": icon_dictionary[social_network.network],
"url": social_network.url,
"clean_url": clean_url,
"placeholder": social_network.username,
}
if social_network.network == "StackOverflow":
username = social_network.username.split("/")[1]
connection["placeholder"] = username
if social_network.network == "Google Scholar":
connection["placeholder"] = "Google Scholar"
connections.append(connection)
return connections
def make_a_url_clean(url: str) -> str:
"""Make a URL clean by removing the protocol, www, and trailing slashes.
+99 -13
View File
@@ -110,7 +110,7 @@ def get_characteristic_entry_attributes(
def get_entry_type_name_and_section_validator(
entry: dict[str, Any] | str, entry_types: list[Type]
entry: dict[str, Any] | str | Type, entry_types: list[Type]
) -> tuple[str, Type[SectionBase]]:
"""Get the entry type name and the section validator based on the entry.
@@ -126,10 +126,12 @@ def get_entry_type_name_and_section_validator(
Returns:
tuple[str, Type[SectionBase]]: The entry type name and the section validator.
"""
characteristic_entry_attributes = get_characteristic_entry_attributes(entry_types)
if isinstance(entry, dict):
entry_type_name = None # the entry type is not determined yet
characteristic_entry_attributes = get_characteristic_entry_attributes(
entry_types
)
for (
EntryType,
@@ -155,10 +157,12 @@ def get_entry_type_name_and_section_validator(
entry_type_name = entry.__class__.__name__
section_type = create_a_section_validator(entry.__class__)
return entry_type_name, section_type
return entry_type_name, section_type # type: ignore
def validate_a_section(sections_input: list[Any], entry_types: list[Type]) -> list[Any]:
def validate_a_section(
sections_input: list[Any], entry_types: list[Type]
) -> list[entry_types.Entry]:
"""Validate a list of entries (a section) based on the entry types.
Sections input is a list of entries. Since there are multiple entry types, it is not
@@ -204,9 +208,10 @@ def validate_a_section(sections_input: list[Any], entry_types: list[Type]) -> li
}
try:
section_type.model_validate(
section_object = section_type.model_validate(
section,
)
sections_input = section_object.entries
except pydantic.ValidationError as e:
new_error = ValueError(
"There are problems with the entries. RenderCV detected the entry type"
@@ -217,6 +222,11 @@ def validate_a_section(sections_input: list[Any], entry_types: list[Type]) -> li
)
raise new_error from e
else:
raise ValueError(
"Each section should be a list of entries! Please see the documentation for"
" more information about the sections.",
)
return sections_input
@@ -261,8 +271,8 @@ ListOfEntries = list[entry_types.Entry]
# can be any of the available entry types. The section is validated with the
# `validate_a_section` function.
SectionContents = Annotated[
ListOfEntries,
pydantic.PlainValidator(
pydantic.json_schema.SkipJsonSchema[Any] | ListOfEntries,
pydantic.BeforeValidator(
lambda entries: validate_a_section(
entries, entry_types=entry_types.available_entry_models
)
@@ -389,11 +399,88 @@ class CurriculumVitae(RenderCVBaseModel):
)
@functools.cached_property
def connections(self) -> list[dict[str, str]]:
def connections(self) -> list[dict[str, Optional[str]]]:
"""Return all the connections of the person as a list of dictionaries and cache
`connections` as an attribute of the instance.
`connections` as an attribute of the instance. The connections are used in the
header of the CV.
Returns:
list[dict[str, Optional[str]]]: The connections of the person.
"""
connections = computers.compute_connections(self)
connections: list[dict[str, Optional[str]]] = []
if self.location is not None:
connections.append(
{
"latex_icon": "\\faMapMarker*",
"url": None,
"clean_url": None,
"placeholder": self.location,
}
)
if self.email is not None:
connections.append(
{
"latex_icon": "\\faEnvelope[regular]",
"url": f"mailto:{self.email}",
"clean_url": self.email,
"placeholder": self.email,
}
)
if self.phone is not None:
phone_placeholder = self.phone.replace("tel:", "").replace("-", " ")
connections.append(
{
"latex_icon": "\\faPhone*",
"url": f"{self.phone}",
"clean_url": phone_placeholder,
"placeholder": phone_placeholder,
}
)
if self.website is not None:
website_placeholder = computers.make_a_url_clean(str(self.website))
connections.append(
{
"latex_icon": "\\faLink",
"url": str(self.website),
"clean_url": website_placeholder,
"placeholder": website_placeholder,
}
)
if self.social_networks is not None:
icon_dictionary = {
"LinkedIn": "\\faLinkedinIn",
"GitHub": "\\faGithub",
"GitLab": "\\faGitlab",
"Instagram": "\\faInstagram",
"Mastodon": "\\faMastodon",
"ORCID": "\\faOrcid",
"StackOverflow": "\\faStackOverflow",
"ResearchGate": "\\faResearchgate",
"YouTube": "\\faYoutube",
"Google Scholar": "\\faGraduationCap",
}
for social_network in self.social_networks:
clean_url = computers.make_a_url_clean(social_network.url)
connection = {
"latex_icon": icon_dictionary[social_network.network],
"url": social_network.url,
"clean_url": clean_url,
"placeholder": social_network.username,
}
if social_network.network == "StackOverflow":
username = social_network.username.split("/")[1]
connection["placeholder"] = username
if social_network.network == "Google Scholar":
connection["placeholder"] = "Google Scholar"
connections.append(connection) # type: ignore
return connections
@@ -406,8 +493,6 @@ class CurriculumVitae(RenderCVBaseModel):
input sections to a list of `SectionBase` objects. This makes it easier to work with
the sections in the rest of the code.
Args:
sections_input (Optional[dict[str, SectionInput]]): The input sections.
Returns:
list[SectionBase]: The computed sections.
"""
@@ -420,7 +505,8 @@ class CurriculumVitae(RenderCVBaseModel):
# The first entry can be used because all the entries in the section are
# already validated with the `validate_a_section` function:
entry_type_name, _ = get_entry_type_name_and_section_validator(
entries[0]
entries[0], # type: ignore
entry_types=entry_types.available_entry_models,
)
# SectionBase is used so that entries are not validated again:
+4 -4
View File
@@ -42,7 +42,7 @@ def validate_design_options(
Returns:
Any: The validated design as a Pydantic data model.
"""
if isinstance(design, available_theme_options):
if isinstance(design, tuple(available_theme_options.values())):
# Then it means it is an already validated built-in theme. Return it as it is:
return design
elif design["theme"] in available_theme_options:
@@ -104,7 +104,7 @@ def validate_design_options(
path_to_init_file,
)
theme_module = importlib.util.module_from_spec(spec)
theme_module = importlib.util.module_from_spec(spec) # type: ignore
try:
spec.loader.exec_module(theme_module) # type: ignore
except SyntaxError:
@@ -158,8 +158,8 @@ RenderCVBuiltinDesign = Annotated[
# themes. However, the JSON Schema generation is skipped, otherwise, the JSON Schema
# would accept any `design` field in the YAML input file.
RenderCVDesign = Annotated[
RenderCVBuiltinDesign | pydantic.json_schema.SkipJsonSchema[Any],
pydantic.PlainValidator(
pydantic.json_schema.SkipJsonSchema[Any] | RenderCVBuiltinDesign,
pydantic.BeforeValidator(
lambda design: validate_design_options(
design,
available_theme_options=available_theme_options,
+8 -8
View File
@@ -70,7 +70,7 @@ def validate_start_and_end_date_fields(
elif date != "present":
# Validate the date:
computers.get_date_object(date)
computers.get_date_object(date)
return date
@@ -106,16 +106,16 @@ def validate_and_adjust_dates_for_an_entry(
start_date = None
end_date = None
elif start_date_is_provided:
start_date = computers.get_date_object(start_date)
start_date_object = computers.get_date_object(start_date)
if not end_date_is_provided:
# If only start_date is provided, assume it is an ongoing event, i.e.,
# the end_date is present:
end_date = "present"
if end_date != "present":
end_date = computers.get_date_object(end_date)
end_date_object = computers.get_date_object(end_date)
if start_date > end_date:
if start_date_object > end_date_object:
raise ValueError(
'"start_date" can not be after "end_date"!',
"start_date", # This is the location of the error
@@ -243,7 +243,7 @@ class PublicationEntryBase(RenderCVBaseModel):
description="The journal or conference name.",
)
@pydantic.model_validator(mode="after")
@pydantic.model_validator(mode="after") # type: ignore
def ignore_url_if_doi_is_given(self) -> "PublicationEntryBase":
"""Check if DOI is provided and ignore the URL if it is provided."""
doi_is_provided = self.doi is not None
@@ -273,7 +273,7 @@ class PublicationEntryBase(RenderCVBaseModel):
url_is_provided = self.url is not None
if url_is_provided:
return computers.make_a_url_clean(self.url)
return computers.make_a_url_clean(self.url) # type: ignore
else:
return ""
@@ -324,7 +324,7 @@ class EntryBase(EntryWithDate):
examples=["Did this.", "Did that."],
)
@pydantic.model_validator(mode="after")
@pydantic.model_validator(mode="after") # type: ignore
def check_and_adjust_dates(self) -> "EntryBase":
"""Call the `validate_adjust_dates_of_an_entry` function to validate the
dates.
@@ -456,7 +456,7 @@ Entry = (
# Entry.__args__[:-1] is a tuple of all the entry types except `str``:
# `str` (TextEntry) is not included because it's validation handled differently. It is
# not a Pydantic model, but a string.
available_entry_models = Entry.__args__[:-1]
available_entry_models = list(Entry.__args__[:-1])
available_entry_type_names = [
entry_type.__name__ for entry_type in available_entry_models
+1 -1
View File
@@ -114,7 +114,7 @@ class LocaleCatalog(RenderCVBaseModel):
def update_translations(cls, value: str, info: pydantic.ValidationInfo) -> str:
"""Update the `locale_catalog` dictionary with the provided translations."""
if value:
locale_catalog[info.field_name] = value
locale_catalog[info.field_name] = value # type: ignore
return value
+11 -2
View File
@@ -9,8 +9,6 @@ import pydantic
from ...themes.classic import ClassicThemeOptions
# Disable Pydantic warnings:
# warnings.filterwarnings("ignore")
from .base import RenderCVBaseModel
from .curriculum_vitae import CurriculumVitae
from .design import RenderCVDesign
@@ -37,4 +35,15 @@ class RenderCVDataModel(RenderCVBaseModel):
description=(
"The locale catalog of the CV to allow the support of multiple languages."
),
validate_default=True,
)
@pydantic.field_validator("locale_catalog")
@classmethod
def initialize_locale_catalog(cls, locale_catalog: LocaleCatalog) -> LocaleCatalog:
"""Even if the locale catalog is not provided, initialize it with the default
values."""
if locale_catalog is None:
LocaleCatalog()
return locale_catalog
+3 -3
View File
@@ -12,13 +12,13 @@ import ruamel.yaml
from . import models
def read_a_yaml_file(file_path_or_contents: pathlib.Path) -> dict[str, Any]:
def read_a_yaml_file(file_path_or_contents: pathlib.Path | str) -> dict[str, Any]:
"""Read a YAML file and return its content as a dictionary. The YAML file can be
given as a path to the file or as the contents of the file as a string.
Args:
file_path_or_contents (pathlib.Path): The path to the YAML file or the contents
of the YAML file as a string.
file_path_or_contents (pathlib.Path | str): The path to the YAML file or the
contents of the YAML file as a string.
Returns:
dict: The content of the YAML file as a dictionary.
"""
+4 -4
View File
@@ -15,8 +15,8 @@ from .renderer import (
render_a_latex_file,
render_a_latex_file_and_copy_theme_files,
render_a_markdown_file,
render_html_from_markdown,
render_pdf_from_latex,
render_an_html_from_markdown,
render_a_pdf_from_latex,
render_pngs_from_pdf,
)
@@ -25,7 +25,7 @@ __all__ = [
"render_a_latex_file",
"render_a_markdown_file",
"render_a_latex_file_and_copy_theme_files",
"render_pdf_from_latex",
"render_a_pdf_from_latex",
"render_pngs_from_pdf",
"render_html_from_markdown",
"render_an_html_from_markdown",
]
+6 -6
View File
@@ -38,7 +38,7 @@ def copy_theme_files_to_output_directory(
else:
# Then it means the theme is a custom theme. If theme_directory is not given
# as an argument, then look for the theme in the current working directory.
theme_directory_path = pathlib.Path(pathlib.Path.cwd()) / theme_name
theme_directory_path = pathlib.Path.cwd() / theme_name
if not theme_directory_path.is_dir():
raise FileNotFoundError(
@@ -51,7 +51,7 @@ def copy_theme_files_to_output_directory(
# theme_file.suffix returns the latest part of the file name after the last dot.
# But we need the latest part of the file name after the first dot:
try:
suffix = re.search(r"\..*", theme_file.name)[0]
suffix = re.search(r"\..*", theme_file.name)[0] # type: ignore
except TypeError:
suffix = ""
@@ -145,7 +145,7 @@ def render_a_latex_file_and_copy_theme_files(
return latex_file_path
def render_pdf_from_latex(
def render_a_pdf_from_latex(
latex_file_path: pathlib.Path, local_latex_command: Optional[str] = None
) -> pathlib.Path:
"""Run TinyTeX with the given $\\LaTeX$ file to render the PDF.
@@ -272,15 +272,15 @@ def render_pngs_from_pdf(pdf_file_path: pathlib.Path) -> list[pathlib.Path]:
png_files = []
pdf = fitz.open(pdf_file_path) # open the PDF file
for page in pdf: # iterate the pages
image = page.get_pixmap(dpi=300)
png_file_path = png_directory / f"{png_file_name}_{page.number+1}.png"
image = page.get_pixmap(dpi=300) # type: ignore
png_file_path = png_directory / f"{png_file_name}_{page.number+1}.png" # type: ignore
image.save(png_file_path)
png_files.append(png_file_path)
return png_files
def render_html_from_markdown(markdown_file_path: pathlib.Path) -> pathlib.Path:
def render_an_html_from_markdown(markdown_file_path: pathlib.Path) -> pathlib.Path:
"""Render an HTML file from a Markdown file with the same name and in the same
directory. It uses `rendercv/themes/main.j2.html` as the Jinja2 template.
+1 -1
View File
@@ -770,7 +770,7 @@ def setup_jinja2_environment() -> jinja2.Environment:
jinja2.Environment: The theme environment.
"""
global jinja2_environment
themes_directory = pathlib.Path(__file__).parent / "themes"
themes_directory = pathlib.Path(__file__).parent.parent / "themes"
if jinja2_environment is None:
# create a Jinja2 environment: