From 920cf379cf7778873e7ee8bfbdb6527d1080c7d4 Mon Sep 17 00:00:00 2001 From: Sina Atalay Date: Mon, 18 Sep 2023 20:22:44 +0200 Subject: [PATCH] add Publications section --- rendercv/data_model.py | 80 +++++++++++++++---- rendercv/rendering.py | 36 +++++++++ .../templates/components/classic/entry.tex.j2 | 19 +++++ .../classic/header_connections.tex.j2 | 2 +- .../classic/section_contents.tex.j2 | 9 +++ 5 files changed, 129 insertions(+), 17 deletions(-) diff --git a/rendercv/data_model.py b/rendercv/data_model.py index c333e56..084a8e1 100644 --- a/rendercv/data_model.py +++ b/rendercv/data_model.py @@ -492,12 +492,6 @@ class Event(BaseModel): ) model.end_date = "present" - elif not start_date_is_provided and not date_is_provided: - raise ValueError( - 'Either "date" or "start_date" and "end_date" should be provided in' - " each entry." - ) - return model @computed_field @@ -678,6 +672,50 @@ class EducationEntry(Event): return highlight_strings +class PublicationEntry(Event): + """This class stores [PublicationEntry](../index.md#publicationentry) information.""" + + title: str = Field( + title="Title of the Publication", + description="The title of the publication. It will be shown as bold text.", + examples=["My Awesome Paper", "My Awesome Book"], + ) + authors: list[str] = Field( + title="Authors", + description="The authors of the publication in order as a list of strings.", + examples=["John Doe", "Jane Doe"], + ) + journal: str = Field( + title="Journal", + description="The journal or the conference name.", + examples=[ + "Physical Review B", + "ASME International Mechanical Engineering Congress and Exposition", + ], + ) + doi: str = Field( + title="DOI", + description="The DOI of the publication.", + examples=["10.1103/PhysRevB.76.054309"], + ) + date: str = Field( + title="Publication Date", + description="The date of the publication.", + examples=[2021, 2022], + ) + cited_by: Optional[int] = Field( + default=None, + title="Cited By", + description="The number of citations of the publication.", + examples=[10, 100], + ) + + @computed_field + @cached_property + def doi_url(self) -> str: + return f"https://doi.org/{self.doi}" + + class SocialNetwork(BaseModel): """This class stores a social network information. @@ -719,10 +757,14 @@ class Connection(BaseModel): elif self.name == "email": url = f"mailto:{self.value}" elif self.name == "website": - url = self.value + url = f"{self.value}" + elif self.name == "phone": + url = f"{self.value}" else: raise RuntimeError(f'"{self.name}" is not a valid connection!"') + return url + class Section(BaseModel): """This class stores a section information.""" @@ -733,15 +775,14 @@ class Section(BaseModel): examples=["Awards", "My Custom Section", "Languages"], ) entry_type: Literal[ - "OneLineEntry", "NormalEntry", "ExperienceEntry", "EducationEntry" + "OneLineEntry", + "NormalEntry", + "ExperienceEntry", + "EducationEntry", + "PublicationEntry", ] = Field( title="Entry Type", - description=( - "The type of the entries in the section. Classic theme supports" - " four types of entries: OneLineEntry, NormalEntry, ExperienceEntry, and" - " EducationEntry." - ), - examples=["OneLineEntry", "NormalEntry", "ExperienceEntry", "EducationEntry"], + description="The type of the entries in the section.", ) link_text: Optional[str] = Field( default=None, @@ -753,7 +794,7 @@ class Section(BaseModel): ), examples=["view on GitHub", "view on LinkedIn"], ) - entries: list[NormalEntry | OneLineEntry | ExperienceEntry | EducationEntry] = ( + entries: list[NormalEntry | OneLineEntry | ExperienceEntry | EducationEntry | PublicationEntry] = ( Field( title="Entries", description=( @@ -827,6 +868,11 @@ class CurriculumVitae(BaseModel): title="Personal Projects", description="The personal project entries of the person.", ) + publications: Optional[list[PublicationEntry]] = Field( + default=None, + title="Publications", + description="The publication entries of the person.", + ) certificates: Optional[list[NormalEntry]] = Field( default=None, title="Certificates", @@ -890,6 +936,7 @@ class CurriculumVitae(BaseModel): "Extracurricular Activities": self.extracurricular_activities, "Test Scores": self.test_scores, "Skills": self.skills, + "Publications": self.publications, } if self.section_order is None: @@ -903,6 +950,7 @@ class CurriculumVitae(BaseModel): "Test Scores", "Certificates", "Extracurricular Activities", + "Publications" ] if self.custom_sections is not None: # If the user specified custom sections, then add them to the end of the @@ -915,7 +963,7 @@ class CurriculumVitae(BaseModel): for section_name in self.section_order: # capitalize the first letter of each word in the section name: section_name = section_name.title() - + # Create a section for each section name in the section order: if section_name in pre_defined_sections: entry_type = pre_defined_sections[section_name][0].__class__.__name__ diff --git a/rendercv/rendering.py b/rendercv/rendering.py index 2fe1f06..2358033 100644 --- a/rendercv/rendering.py +++ b/rendercv/rendering.py @@ -16,6 +16,8 @@ import rendercv.templates def markdown_to_latex(markdown_string: str) -> str: """Convert a markdown string to LaTeX. + This function is used as a Jinja2 filter. + Example: ```python markdown_to_latex("This is a **bold** text with an [*italic link*](https://google.com).") @@ -72,6 +74,8 @@ def markdown_to_latex(markdown_string: str) -> str: def markdown_url_to_url(value: str) -> bool: """Convert a markdown link to a normal string URL. + This function is used as a Jinja2 filter. + Example: ```python markdown_url_to_url("[Google](https://google.com)") @@ -98,6 +102,37 @@ def markdown_url_to_url(value: str) -> bool: raise ValueError("markdown_url_to_url should only be used on markdown links!") +def make_it_bold(value: str, match_str: str) -> str: + """Make the matched parts of the string bold. + + This function is used as a Jinja2 filter. + + Example: + ```python + make_it_bold_if("Hello World!", "Hello") + ``` + + will return: + + `#!python "\\textbf{Hello} World!"` + + Args: + value (str): The string to make bold. + match_str (str): The string to match. + """ + if not isinstance(value, str): + raise ValueError("make_it_bold_if should only be used on strings!") + + if not isinstance(match_str, str): + raise ValueError("The string to match should be a string!") + + if match_str in value: + value.replace(match_str, "\\textbf{" + match_str + "}") + return value + else: + return value + + def render_template(data): """Render the template using the given data. @@ -134,6 +169,7 @@ def render_template(data): # add custom filters: environment.filters["markdown_to_latex"] = markdown_to_latex environment.filters["markdown_url_to_url"] = markdown_url_to_url + environment.filters["make_it_bold"] = make_it_bold output_latex_file = template.render(design=data.design.options, cv=data.cv) diff --git a/rendercv/templates/components/classic/entry.tex.j2 b/rendercv/templates/components/classic/entry.tex.j2 index bda4bb4..84b6c3e 100644 --- a/rendercv/templates/components/classic/entry.tex.j2 +++ b/rendercv/templates/components/classic/entry.tex.j2 @@ -55,6 +55,25 @@ \end{tabularx} ((* endmacro *)) +((* macro publication(title, authors, journal, year, doi, doi_url)*)) +((# \begin{tabularx}{⟨width⟩}[⟨pos⟩]{⟨preamble⟩} #)) + ((# width: \textwidth #)) + ((# preamble: first column, second column #)) + ((# first column:: K{<>}; variable width, ragged left column #)) + ((# second column: R{<>}; constant width ragged right column #)) +\begin{tabularx}{\textwidth}{K{<>} R{2 cm}} + \textbf{<>} + + <<authors|join(", ")|make_it_bold("Cetin Yilmaz")>> + + DOI: \hrefExternal{<<doi_url>>}{<<doi>>} + + <<journal>> + & + <<year>> +\end{tabularx} +((* endmacro *)) + ((* macro one_line(name, details, markdown_url=none, link_text=none)*)) \setlength{\leftskip}{0.2cm} diff --git a/rendercv/templates/components/classic/header_connections.tex.j2 b/rendercv/templates/components/classic/header_connections.tex.j2 index 2795e8e..e95b471 100644 --- a/rendercv/templates/components/classic/header_connections.tex.j2 +++ b/rendercv/templates/components/classic/header_connections.tex.j2 @@ -2,7 +2,7 @@ ((* macro LinkedIn(username, url) *))\href{<<url>>}{{\small\faLinkedinIn}\hspace{0.13cm}<<username>>}((* endmacro *)) ((* macro GitHub(username, url) *))\href{<<url>>}{{\small\faGithub}\hspace{0.13cm}<<username>>}((* endmacro *)) ((* macro Instagram(username, url) *))\href{}{{\small\faInstagram}\hspace{0.13cm}<<username>>}((* endmacro *)) -((* macro phone(number, url) *)){\footnotesize\faPhone*}\hspace{0.13cm}<<number|replace("tel:", "")|replace("-"," ")>>((* endmacro *)) +((* macro phone(number, url) *))\href{<<url>>}{{\footnotesize\faPhone*}\hspace{0.13cm}<<number|replace("tel:", "")|replace("-"," ")>>}((* endmacro *)) ((* macro email(email, url) *))\href{<<url>>}{{\small\faEnvelope[regular]}\hspace{0.13cm}<<email>>}((* endmacro *)) ((* macro website(url, dummy) *))\href{<<url>>}{{\small\faLink}\hspace{0.13cm}<<url|replace("https://","")|replace("/","")>>}((* endmacro *)) diff --git a/rendercv/templates/components/classic/section_contents.tex.j2 b/rendercv/templates/components/classic/section_contents.tex.j2 index 92c083d..841e09d 100644 --- a/rendercv/templates/components/classic/section_contents.tex.j2 +++ b/rendercv/templates/components/classic/section_contents.tex.j2 @@ -36,6 +36,15 @@ details=value.details, markdown_url=value.markdown_url, link_text=link_text, + )|indent(4)>> + ((* elif entry_type == "PublicationEntry" *)) + <<entry["publication"]( + title=value.title, + authors=value.authors, + journal=value.journal, + year=value.date, + doi=value.doi, + doi_url=value.doi_url, )|indent(4)>> ((* endif *)) ((* if not loop.last *))