mirror of https://github.com/eyhc1/rendercv.git
escape LaTeX characters
This commit is contained in:
parent
30c553c4de
commit
c42e524c19
|
@ -92,14 +92,14 @@ def check_spelling(sentence: str) -> str:
|
||||||
Returns:
|
Returns:
|
||||||
str: The same sentence.
|
str: The same sentence.
|
||||||
"""
|
"""
|
||||||
modifiedSentence = sentence.lower() # convert to lower case
|
modified_sentence = sentence.lower() # convert to lower case
|
||||||
modifiedSentence = re.sub(
|
modified_sentence = re.sub(
|
||||||
r"\-+", " ", modifiedSentence
|
r"\-+", " ", modified_sentence
|
||||||
) # replace hyphens with spaces
|
) # replace hyphens with spaces
|
||||||
modifiedSentence = re.sub(
|
modified_sentence = re.sub(
|
||||||
r"[^a-z\s\-']", "", modifiedSentence
|
r"[^a-z\s\-']", "", modified_sentence
|
||||||
) # remove all the special characters
|
) # remove all the special characters
|
||||||
words = modifiedSentence.split() # split sentence into a list of words
|
words = modified_sentence.split() # split sentence into a list of words
|
||||||
misspelled = spell.unknown(words) # find misspelled words
|
misspelled = spell.unknown(words) # find misspelled words
|
||||||
|
|
||||||
if len(misspelled) > 0:
|
if len(misspelled) > 0:
|
||||||
|
@ -120,6 +120,40 @@ def check_spelling(sentence: str) -> str:
|
||||||
return sentence
|
return sentence
|
||||||
|
|
||||||
|
|
||||||
|
def escape_latex_characters(sentence: str) -> str:
|
||||||
|
"""Escape LaTeX characters in a sentence.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
escape_latex_characters("This is a # sentence.")
|
||||||
|
```
|
||||||
|
will return:
|
||||||
|
`#!python "This is a \\# sentence."`
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Dictionary of escape characters:
|
||||||
|
escape_characters = {
|
||||||
|
"#": r"\#",
|
||||||
|
"$": r"\$",
|
||||||
|
"%": r"\%",
|
||||||
|
"&": r"\&",
|
||||||
|
"~": r"\textasciitilde",
|
||||||
|
"_": r"\_",
|
||||||
|
"^": r"\textasciicircum",
|
||||||
|
"{": r"\{",
|
||||||
|
"}": r"\}",
|
||||||
|
"\\": r"\textbackslash",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Loop through the letters of the sentence and if you find an escape character,
|
||||||
|
# replace it with its LaTeX equivalent:
|
||||||
|
for character in sentence:
|
||||||
|
if character in escape_characters:
|
||||||
|
sentence = sentence.replace(character, escape_characters[character])
|
||||||
|
|
||||||
|
return sentence
|
||||||
|
|
||||||
|
|
||||||
def compute_time_span_string(start_date: Date, end_date: Date) -> str:
|
def compute_time_span_string(start_date: Date, end_date: Date) -> str:
|
||||||
"""Compute the time span between two dates and return a string that represents it.
|
"""Compute the time span between two dates and return a string that represents it.
|
||||||
|
|
||||||
|
@ -303,7 +337,8 @@ LaTeXDimension = Annotated[
|
||||||
pattern=r"\d+\.?\d* *(cm|in|pt|mm|ex|em)",
|
pattern=r"\d+\.?\d* *(cm|in|pt|mm|ex|em)",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
SpellCheckedString = Annotated[str, AfterValidator(check_spelling)]
|
LaTeXString = Annotated[str, AfterValidator(escape_latex_characters)]
|
||||||
|
SpellCheckedString = Annotated[LaTeXString, AfterValidator(check_spelling)]
|
||||||
|
|
||||||
# ======================================================================================
|
# ======================================================================================
|
||||||
# ======================================================================================
|
# ======================================================================================
|
||||||
|
@ -530,7 +565,9 @@ class Design(BaseModel):
|
||||||
# Go to fonts directory and check if the font exists:
|
# Go to fonts directory and check if the font exists:
|
||||||
fonts_directory = str(files("rendercv").joinpath("templates", "fonts"))
|
fonts_directory = str(files("rendercv").joinpath("templates", "fonts"))
|
||||||
if font not in os.listdir(fonts_directory):
|
if font not in os.listdir(fonts_directory):
|
||||||
raise ValueError(f'The font "{font}" is not found in the "fonts" directory 🥴')
|
raise ValueError(
|
||||||
|
f'The font "{font}" is not found in the "fonts" directory 🥴'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
font_directory = os.path.join(fonts_directory, font)
|
font_directory = os.path.join(fonts_directory, font)
|
||||||
required_files = [
|
required_files = [
|
||||||
|
@ -590,7 +627,7 @@ class Event(BaseModel):
|
||||||
),
|
),
|
||||||
examples=["2020-09-24", "present"],
|
examples=["2020-09-24", "present"],
|
||||||
)
|
)
|
||||||
date: Optional[str | PastDate] = Field(
|
date: Optional[LaTeXString | PastDate] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Date",
|
title="Date",
|
||||||
description=(
|
description=(
|
||||||
|
@ -609,7 +646,7 @@ class Event(BaseModel):
|
||||||
),
|
),
|
||||||
examples=["Did this.", "Did that."],
|
examples=["Did this.", "Did that."],
|
||||||
)
|
)
|
||||||
location: Optional[str] = Field(
|
location: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Location",
|
title="Location",
|
||||||
description=(
|
description=(
|
||||||
|
@ -622,7 +659,7 @@ class Event(BaseModel):
|
||||||
|
|
||||||
@field_validator("date")
|
@field_validator("date")
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_date(cls, date: str | PastDate) -> str | PastDate:
|
def check_date(cls, date: LaTeXString | PastDate) -> LaTeXString | PastDate:
|
||||||
if isinstance(date, str):
|
if isinstance(date, str):
|
||||||
try:
|
try:
|
||||||
# If this runs, it means the date is an ISO format string, and it can be
|
# If this runs, it means the date is an ISO format string, and it can be
|
||||||
|
@ -695,7 +732,7 @@ class Event(BaseModel):
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@cached_property
|
@cached_property
|
||||||
def date_and_location_strings_with_timespan(self) -> list[str]:
|
def date_and_location_strings_with_timespan(self) -> list[LaTeXString]:
|
||||||
date_and_location_strings = []
|
date_and_location_strings = []
|
||||||
|
|
||||||
if self.location is not None:
|
if self.location is not None:
|
||||||
|
@ -732,7 +769,7 @@ class Event(BaseModel):
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@cached_property
|
@cached_property
|
||||||
def date_and_location_strings_without_timespan(self) -> list[str]:
|
def date_and_location_strings_without_timespan(self) -> list[LaTeXString]:
|
||||||
# use copy() to avoid modifying the original list
|
# use copy() to avoid modifying the original list
|
||||||
date_and_location_strings = self.date_and_location_strings_with_timespan.copy()
|
date_and_location_strings = self.date_and_location_strings_with_timespan.copy()
|
||||||
for string in date_and_location_strings:
|
for string in date_and_location_strings:
|
||||||
|
@ -780,7 +817,7 @@ class Event(BaseModel):
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@cached_property
|
@cached_property
|
||||||
def month_and_year(self) -> Optional[str]:
|
def month_and_year(self) -> Optional[LaTeXString]:
|
||||||
if self.date is not None:
|
if self.date is not None:
|
||||||
# Then it means start_date and end_date are not provided.
|
# Then it means start_date and end_date are not provided.
|
||||||
try:
|
try:
|
||||||
|
@ -800,7 +837,7 @@ class Event(BaseModel):
|
||||||
class OneLineEntry(Event):
|
class OneLineEntry(Event):
|
||||||
"""This class stores [OneLineEntry](../user_guide.md#onelineentry) information."""
|
"""This class stores [OneLineEntry](../user_guide.md#onelineentry) information."""
|
||||||
|
|
||||||
name: str = Field(
|
name: LaTeXString = Field(
|
||||||
title="Name",
|
title="Name",
|
||||||
description="The name of the entry. It will be shown as bold text.",
|
description="The name of the entry. It will be shown as bold text.",
|
||||||
)
|
)
|
||||||
|
@ -813,7 +850,7 @@ class OneLineEntry(Event):
|
||||||
class NormalEntry(Event):
|
class NormalEntry(Event):
|
||||||
"""This class stores [NormalEntry](../user_guide.md#normalentry) information."""
|
"""This class stores [NormalEntry](../user_guide.md#normalentry) information."""
|
||||||
|
|
||||||
name: str = Field(
|
name: LaTeXString = Field(
|
||||||
title="Name",
|
title="Name",
|
||||||
description="The name of the entry. It will be shown as bold text.",
|
description="The name of the entry. It will be shown as bold text.",
|
||||||
)
|
)
|
||||||
|
@ -822,11 +859,11 @@ class NormalEntry(Event):
|
||||||
class ExperienceEntry(Event):
|
class ExperienceEntry(Event):
|
||||||
"""This class stores [ExperienceEntry](../user_guide.md#experienceentry) information."""
|
"""This class stores [ExperienceEntry](../user_guide.md#experienceentry) information."""
|
||||||
|
|
||||||
company: str = Field(
|
company: LaTeXString = Field(
|
||||||
title="Company",
|
title="Company",
|
||||||
description="The company name. It will be shown as bold text.",
|
description="The company name. It will be shown as bold text.",
|
||||||
)
|
)
|
||||||
position: str = Field(
|
position: LaTeXString = Field(
|
||||||
title="Position",
|
title="Position",
|
||||||
description="The position. It will be shown as normal text.",
|
description="The position. It will be shown as normal text.",
|
||||||
)
|
)
|
||||||
|
@ -835,22 +872,22 @@ class ExperienceEntry(Event):
|
||||||
class EducationEntry(Event):
|
class EducationEntry(Event):
|
||||||
"""This class stores [EducationEntry](../user_guide.md#educationentry) information."""
|
"""This class stores [EducationEntry](../user_guide.md#educationentry) information."""
|
||||||
|
|
||||||
institution: str = Field(
|
institution: LaTeXString = Field(
|
||||||
title="Institution",
|
title="Institution",
|
||||||
description="The institution name. It will be shown as bold text.",
|
description="The institution name. It will be shown as bold text.",
|
||||||
examples=["Bogazici University"],
|
examples=["Bogazici University"],
|
||||||
)
|
)
|
||||||
area: str = Field(
|
area: LaTeXString = Field(
|
||||||
title="Area",
|
title="Area",
|
||||||
description="The area of study. It will be shown as normal text.",
|
description="The area of study. It will be shown as normal text.",
|
||||||
)
|
)
|
||||||
study_type: Optional[str] = Field(
|
study_type: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Study Type",
|
title="Study Type",
|
||||||
description="The type of the degree.",
|
description="The type of the degree.",
|
||||||
examples=["BS", "BA", "PhD", "MS"],
|
examples=["BS", "BA", "PhD", "MS"],
|
||||||
)
|
)
|
||||||
gpa: Optional[str | float] = Field(
|
gpa: Optional[LaTeXString | float] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="GPA",
|
title="GPA",
|
||||||
description="The GPA of the degree.",
|
description="The GPA of the degree.",
|
||||||
|
@ -884,11 +921,11 @@ class EducationEntry(Event):
|
||||||
class PublicationEntry(Event):
|
class PublicationEntry(Event):
|
||||||
"""This class stores [PublicationEntry](../user_guide.md#publicationentry) information."""
|
"""This class stores [PublicationEntry](../user_guide.md#publicationentry) information."""
|
||||||
|
|
||||||
title: str = Field(
|
title: LaTeXString = Field(
|
||||||
title="Title of the Publication",
|
title="Title of the Publication",
|
||||||
description="The title of the publication. It will be shown as bold text.",
|
description="The title of the publication. It will be shown as bold text.",
|
||||||
)
|
)
|
||||||
authors: list[str] = Field(
|
authors: list[LaTeXString] = Field(
|
||||||
title="Authors",
|
title="Authors",
|
||||||
description="The authors of the publication in order as a list of strings.",
|
description="The authors of the publication in order as a list of strings.",
|
||||||
)
|
)
|
||||||
|
@ -897,7 +934,7 @@ class PublicationEntry(Event):
|
||||||
description="The DOI of the publication.",
|
description="The DOI of the publication.",
|
||||||
examples=["10.48550/arXiv.2310.03138"],
|
examples=["10.48550/arXiv.2310.03138"],
|
||||||
)
|
)
|
||||||
date: str = Field(
|
date: LaTeXString = Field(
|
||||||
title="Publication Date",
|
title="Publication Date",
|
||||||
description="The date of the publication.",
|
description="The date of the publication.",
|
||||||
examples=["2021-10-31"],
|
examples=["2021-10-31"],
|
||||||
|
@ -907,7 +944,7 @@ class PublicationEntry(Event):
|
||||||
title="Cited By",
|
title="Cited By",
|
||||||
description="The number of citations of the publication.",
|
description="The number of citations of the publication.",
|
||||||
)
|
)
|
||||||
journal: Optional[str] = Field(
|
journal: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Journal",
|
title="Journal",
|
||||||
description="The journal or the conference name.",
|
description="The journal or the conference name.",
|
||||||
|
@ -995,7 +1032,7 @@ class Connection(BaseModel):
|
||||||
class Section(BaseModel):
|
class Section(BaseModel):
|
||||||
"""This class stores a section information."""
|
"""This class stores a section information."""
|
||||||
|
|
||||||
title: str = Field(
|
title: LaTeXString = Field(
|
||||||
title="Section Title",
|
title="Section Title",
|
||||||
description="The title of the section.",
|
description="The title of the section.",
|
||||||
examples=["My Custom Section"],
|
examples=["My Custom Section"],
|
||||||
|
@ -1010,7 +1047,7 @@ class Section(BaseModel):
|
||||||
title="Entry Type",
|
title="Entry Type",
|
||||||
description="The type of the entries in the section.",
|
description="The type of the entries in the section.",
|
||||||
)
|
)
|
||||||
link_text: Optional[str] = Field(
|
link_text: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Link Text",
|
title="Link Text",
|
||||||
description=(
|
description=(
|
||||||
|
@ -1029,23 +1066,23 @@ class Section(BaseModel):
|
||||||
|
|
||||||
@field_validator("title")
|
@field_validator("title")
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_first_letters_uppercase(cls, title: str) -> str:
|
def make_first_letters_uppercase(cls, title: LaTeXString) -> LaTeXString:
|
||||||
return title.title()
|
return title.title()
|
||||||
|
|
||||||
|
|
||||||
class CurriculumVitae(BaseModel):
|
class CurriculumVitae(BaseModel):
|
||||||
"""This class bindes all the information of a CV together."""
|
"""This class bindes all the information of a CV together."""
|
||||||
|
|
||||||
name: str = Field(
|
name: LaTeXString = Field(
|
||||||
title="Name",
|
title="Name",
|
||||||
description="The name of the person.",
|
description="The name of the person.",
|
||||||
)
|
)
|
||||||
label: Optional[str] = Field(
|
label: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Label",
|
title="Label",
|
||||||
description="The label of the person.",
|
description="The label of the person.",
|
||||||
)
|
)
|
||||||
location: Optional[str] = Field(
|
location: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Location",
|
title="Location",
|
||||||
description="The location of the person. This is not rendered currently.",
|
description="The location of the person. This is not rendered currently.",
|
||||||
|
@ -1064,7 +1101,7 @@ class CurriculumVitae(BaseModel):
|
||||||
"The social networks of the person. They will be rendered in the heading."
|
"The social networks of the person. They will be rendered in the heading."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
summary: Optional[str] = Field(
|
summary: Optional[LaTeXString] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
title="Summary",
|
title="Summary",
|
||||||
description="The summary of the person.",
|
description="The summary of the person.",
|
||||||
|
@ -1164,7 +1201,7 @@ class CurriculumVitae(BaseModel):
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@cached_property
|
@cached_property
|
||||||
def connections(self) -> list[str]:
|
def connections(self) -> list[Connection]:
|
||||||
connections = []
|
connections = []
|
||||||
if self.location is not None:
|
if self.location is not None:
|
||||||
connections.append(Connection(name="location", value=self.location))
|
connections.append(Connection(name="location", value=self.location))
|
||||||
|
|
Loading…
Reference in New Issue