diff --git a/John_Doe_CV.pdf b/John_Doe_CV.pdf deleted file mode 100644 index fb6425f..0000000 Binary files a/John_Doe_CV.pdf and /dev/null differ diff --git a/schema.json b/docs/schema.json similarity index 100% rename from schema.json rename to docs/schema.json diff --git a/John_Doe_CV.yaml b/examples/John_Doe_CV.yaml similarity index 100% rename from John_Doe_CV.yaml rename to examples/John_Doe_CV.yaml diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..91129fa --- /dev/null +++ b/examples/README.md @@ -0,0 +1 @@ +Explain here it can be used with Python scripts or with the command line. \ No newline at end of file diff --git a/run_rendercv.py b/examples/run_rendercv.py similarity index 100% rename from run_rendercv.py rename to examples/run_rendercv.py diff --git a/rendercv/__init__.py b/rendercv/__init__.py index 690490c..8b4b103 100644 --- a/rendercv/__init__.py +++ b/rendercv/__init__.py @@ -1,8 +1,6 @@ """RenderCV package. -It parses the user input YAML/JSON file and validates the data (checks if the -dates are consistent, if the URLs are valid, etc.). Then, with the data, it creates a -$\\LaTeX$ file and renders it with [TinyTeX](https://yihui.org/tinytex/). +To be continued... """ import logging import os diff --git a/rendercv/data_model.py b/rendercv/data_model.py deleted file mode 100644 index 32b60d9..0000000 --- a/rendercv/data_model.py +++ /dev/null @@ -1,1571 +0,0 @@ -""" -This module contains classes and functions to parse and validate YAML or JSON input -files. It uses [Pydantic](https://github.com/pydantic/pydantic) to achieve this goal. -All the data classes have `BaseModel` from Pydantic as a base class, and some data -fields have advanced types like `HttpUrl`, `EmailStr`, or `PastDate` from the Pydantic -library for validation. -""" - -from datetime import date as Date -from typing import Literal -from typing_extensions import Annotated, Optional -import re -import logging -from functools import cached_property -import urllib.request -import os -from importlib.resources import files -import json -import time - -from pydantic import ( - BaseModel, - HttpUrl, - Field, - field_validator, - model_validator, - computed_field, - EmailStr, -) -from pydantic.json_schema import GenerateJsonSchema -from pydantic.functional_validators import AfterValidator -from pydantic_extra_types.phone_numbers import PhoneNumber -from pydantic_extra_types.color import Color -from ruamel.yaml import YAML - -logger = logging.getLogger(__name__) - - -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"\$", # Don't escape $ as it is used for math mode - "%": r"\%", - "&": r"\&", - "~": r"\textasciitilde{}", - # "_": r"\_", # Don't escape _ as it is used for math mode - # "^": r"\textasciicircum{}", # Don't escape ^ as it is used for math mode - } - - # Don't escape links as hyperref will do it automatically: - - # Find all the links in the sentence: - links = re.findall(r"\[.*?\]\(.*?\)", sentence) - - # Replace the links with a placeholder: - for link in links: - sentence = sentence.replace(link, "!!-link-!!") - - # Handle backslash and curly braces separately because the other characters are - # escaped with backslash and curly braces: - # --don't escape curly braces as they are used heavily in LaTeX--: - # sentence = sentence.replace("{", ">>{") - # sentence = sentence.replace("}", ">>}") - # --don't escape backslash as it is used heavily in LaTeX--: - # sentence = sentence.replace("\\", "\\textbackslash{}") - # sentence = sentence.replace(">>{", "\\{") - # sentence = sentence.replace(">>}", "\\}") - - # Loop through the letters of the sentence and if you find an escape character, - # replace it with its LaTeX equivalent: - copy_of_the_sentence = sentence - for character in copy_of_the_sentence: - if character in escape_characters: - sentence = sentence.replace(character, escape_characters[character]) - - # Replace the links with the original links: - for link in links: - sentence = sentence.replace("!!-link-!!", link) - - return sentence - - -def parse_date_string(date_string: str) -> Date | int: - """Parse a date string in YYYY-MM-DD, YYYY-MM, or YYYY format and return a - datetime.date object. - - Args: - date_string (str): The date string to parse. - Returns: - datetime.date: The parsed date. - """ - if re.match(r"\d{4}-\d{2}-\d{2}", date_string): - # Then it is in YYYY-MM-DD format - date = Date.fromisoformat(date_string) - elif re.match(r"\d{4}-\d{2}", date_string): - # Then it is in YYYY-MM format - # Assign a random day since days are not rendered in the CV - date = Date.fromisoformat(f"{date_string}-01") - elif re.match(r"\d{4}", date_string): - # Then it is in YYYY format - # Then keep it as an integer - date = int(date_string) - else: - raise ValueError( - f'The date string "{date_string}" is not in YYYY-MM-DD, YYYY-MM, or YYYY' - " format." - ) - - if isinstance(date, Date): - # Then it means the date is a Date object, so check if it is a past date: - if date > Date.today(): - raise ValueError( - f'The date "{date_string}" is in the future. Please check the dates.' - ) - - return date - - -def compute_time_span_string(start_date: Date | int, end_date: Date | int) -> str: - """Compute the time span between two dates and return a string that represents it. - - Example: - ```python - compute_time_span_string(Date(2022,9,24), Date(2025,2,12)) - ``` - - will return: - - `#!python "2 years 5 months"` - - Args: - start_date (Date | int): The start date. - end_date (Date | int): The end date. - - Returns: - str: The time span string. - """ - # check if the types of start_date and end_date are correct: - if not isinstance(start_date, (Date, int)): - raise TypeError("start_date is not a Date object or an integer!") - if not isinstance(end_date, (Date, int)): - raise TypeError("end_date is not a Date object or an integer!") - - # calculate the number of days between start_date and end_date: - if isinstance(start_date, Date) and isinstance(end_date, Date): - timespan_in_days = (end_date - start_date).days - elif isinstance(start_date, Date) and isinstance(end_date, int): - timespan_in_days = (Date(end_date, 1, 1) - start_date).days - elif isinstance(start_date, int) and isinstance(end_date, Date): - timespan_in_days = (end_date - Date(start_date, 1, 1)).days - elif isinstance(start_date, int) and isinstance(end_date, int): - timespan_in_days = (end_date - start_date) * 365 - - if timespan_in_days < 0: - raise ValueError( - '"start_date" can not be after "end_date". Please check the dates.' - ) - - # calculate the number of years between start_date and end_date: - how_many_years = timespan_in_days // 365 - if how_many_years == 0: - how_many_years_string = None - elif how_many_years == 1: - how_many_years_string = "1 year" - else: - how_many_years_string = f"{how_many_years} years" - - # calculate the number of months between start_date and end_date: - how_many_months = round((timespan_in_days % 365) / 30) - if how_many_months <= 1: - how_many_months_string = "1 month" - else: - how_many_months_string = f"{how_many_months} months" - - # combine howManyYearsString and howManyMonthsString: - if how_many_years_string is None: - timespan_string = how_many_months_string - else: - timespan_string = f"{how_many_years_string} {how_many_months_string}" - - return timespan_string - - -def format_date(date: Date) -> str: - """Formats a date to a string in the following format: "Jan. 2021". - - It uses month abbreviations, taken from - [Yale University Library](https://web.library.yale.edu/cataloging/months). - - Example: - ```python - format_date(Date(2024,5,1)) - ``` - will return - - `#!python "May 2024"` - - Args: - date (Date): The date to format. - - Returns: - str: The formatted date. - """ - if not isinstance(date, (Date, int)): - raise TypeError("date is not a Date object or an integer!") - - if isinstance(date, int): - # Then it means the user only provided the year, so just return the year - return str(date) - - # Month abbreviations, - # taken from: https://web.library.yale.edu/cataloging/months - abbreviations_of_months = [ - "Jan.", - "Feb.", - "Mar.", - "Apr.", - "May", - "June", - "July", - "Aug.", - "Sept.", - "Oct.", - "Nov.", - "Dec.", - ] - - month = int(date.strftime("%m")) - monthAbbreviation = abbreviations_of_months[month - 1] - year = date.strftime("%Y") - date_string = f"{monthAbbreviation} {year}" - - return date_string - - -def generate_json_schema(output_directory: str) -> str: - """Generate the JSON schema of the data model and save it to a file. - - Args: - output_directory (str): The output directory to save the schema. - """ - - class RenderCVSchemaGenerator(GenerateJsonSchema): - def generate(self, schema, mode="validation"): - json_schema = super().generate(schema, mode=mode) - json_schema["title"] = "RenderCV Input" - - # remove the description of the class (RenderCVDataModel) - del json_schema["description"] - - # add $id - json_schema[ - "$id" - ] = "https://raw.githubusercontent.com/sinaatalay/rendercv/main/schema.json" - - # add $schema - json_schema["$schema"] = "http://json-schema.org/draft-07/schema#" - - # Loop through $defs and remove docstring descriptions and fix optional - # fields - for key, value in json_schema["$defs"].items(): - # Don't allow additional properties - value["additionalProperties"] = False - - # I don't want the docstrings in the schema, so remove them: - if "This class" in value["description"]: - del value["description"] - - # If a type is optional, then Pydantic sets the type to a list of two - # types, one of which is null. The null type can be removed since we - # already have the required field. Moreover, we would like to warn - # users if they provide null values. They can remove the fields if they - # don't want to provide them. - null_type_dict = {} - null_type_dict["type"] = "null" - for field in value["properties"].values(): - if "anyOf" in field: - if ( - len(field["anyOf"]) == 2 - and null_type_dict in field["anyOf"] - ): - field["allOf"] = [field["anyOf"][0]] - del field["anyOf"] - - # In date field, we both accept normal strings and Date objects. They - # are both strings, therefore, if user provides a Date object, then - # JSON schema will complain that it matches two different types. - # Remember that all of the anyOfs are changed to oneOfs. Only one of - # the types can be matched. Therefore, we remove the first type, which - # is the string with the YYYY-MM-DD format. - if ( - "date" in value["properties"] - and "anyOf" in value["properties"]["date"] - ): - del value["properties"]["date"]["anyOf"][0] - - return json_schema - - schema = RenderCVDataModel.model_json_schema( - schema_generator=RenderCVSchemaGenerator - ) - schema = json.dumps(schema, indent=2) - - # Change all anyOf to oneOf - schema = schema.replace('"anyOf"', '"oneOf"') - - path_to_schema = os.path.join(output_directory, "schema.json") - with open(path_to_schema, "w") as f: - f.write(schema) - - return path_to_schema - - -# ====================================================================================== -# DESIGN MODELS ======================================================================== -# ====================================================================================== - -# To understand how to create custom data types, see: -# https://docs.pydantic.dev/latest/usage/types/custom/ -LaTeXDimension = Annotated[ - str, - Field( - pattern=r"\d+\.?\d* *(cm|in|pt|mm|ex|em)", - ), -] - - -class ClassicThemePageMargins(BaseModel): - """This class stores the margins of pages for the classic theme.""" - - top: LaTeXDimension = Field( - default="2 cm", - title="Top Margin", - description="The top margin of the page with units.", - ) - bottom: LaTeXDimension = Field( - default="2 cm", - title="Bottom Margin", - description="The bottom margin of the page with units.", - ) - left: LaTeXDimension = Field( - default="1.24 cm", - title="Left Margin", - description="The left margin of the page with units.", - ) - right: LaTeXDimension = Field( - default="1.24 cm", - title="Right Margin", - description="The right margin of the page with units.", - ) - - -class ClassicThemeSectionTitleMargins(BaseModel): - """This class stores the margins of section titles for the classic theme.""" - - top: LaTeXDimension = Field( - default="0.2 cm", - title="Top Margin", - description="The top margin of section titles.", - ) - bottom: LaTeXDimension = Field( - default="0.2 cm", - title="Bottom Margin", - description="The bottom margin of section titles.", - ) - - -class ClassicThemeEntryAreaMargins(BaseModel): - """This class stores the margins of entry areas for the classic theme. - - For the classic theme, entry areas are [OneLineEntry](../user_guide.md#onelineentry), - [NormalEntry](../user_guide.md#normalentry), and - [ExperienceEntry](../user_guide.md#experienceentry). - """ - - left_and_right: LaTeXDimension = Field( - default="0.2 cm", - title="Left Margin", - description="The left margin of entry areas.", - ) - - vertical_between: LaTeXDimension = Field( - default="0.12 cm", - title="Vertical Margin Between Entry Areas", - description="The vertical margin between entry areas.", - ) - - -class ClassicThemeHighlightsAreaMargins(BaseModel): - """This class stores the margins of highlights areas for the classic theme.""" - - top: LaTeXDimension = Field( - default="0.10 cm", - title="Top Margin", - description="The top margin of highlights areas.", - ) - left: LaTeXDimension = Field( - default="0.4 cm", - title="Left Margin", - description="The left margin of highlights areas.", - ) - vertical_between_bullet_points: LaTeXDimension = Field( - default="0.10 cm", - title="Vertical Margin Between Bullet Points", - description="The vertical margin between bullet points.", - ) - - -class ClassicThemeHeaderMargins(BaseModel): - """This class stores the margins of the header for the classic theme.""" - - vertical_between_name_and_connections: LaTeXDimension = Field( - default="0.2 cm", - title="Vertical Margin Between the Name and Connections", - description=( - "The vertical margin between the name of the person and the connections." - ), - ) - bottom: LaTeXDimension = Field( - default="0.2 cm", - title="Bottom Margin", - description=( - "The bottom margin of the header, i.e., the vertical margin between the" - " connections and the first section title." - ), - ) - - -class ClassicThemeMargins(BaseModel): - """This class stores the margins for the classic theme.""" - - page: ClassicThemePageMargins = Field( - default=ClassicThemePageMargins(), - title="Page Margins", - description="Page margins for the classic theme.", - ) - section_title: ClassicThemeSectionTitleMargins = Field( - default=ClassicThemeSectionTitleMargins(), - title="Section Title Margins", - description="Section title margins for the classic theme.", - ) - entry_area: ClassicThemeEntryAreaMargins = Field( - default=ClassicThemeEntryAreaMargins(), - title="Entry Area Margins", - description="Entry area margins for the classic theme.", - ) - highlights_area: ClassicThemeHighlightsAreaMargins = Field( - default=ClassicThemeHighlightsAreaMargins(), - title="Highlights Area Margins", - description="Highlights area margins for the classic theme.", - ) - header: ClassicThemeHeaderMargins = Field( - default=ClassicThemeHeaderMargins(), - title="Header Margins", - description="Header margins for the classic theme.", - ) - - -class ClassicThemeOptions(BaseModel): - """This class stores the options for the classic theme. - - In RenderCV, each theme has its own Pydantic class so that new themes - can be implemented easily in future. - """ - - primary_color: Color = Field( - default="rgb(0,79,144)", - validate_default=True, - title="Primary Color", - description=( - "The primary color of Classic Theme. It is used for the section titles," - " heading, and the links.\nThe color can be specified either with their" - " [name](https://www.w3.org/TR/SVG11/types.html#ColorKeywords), hexadecimal" - " value, RGB value, or HSL value." - ), - examples=["Black", "7fffd4", "rgb(0,79,144)", "hsl(270, 60%, 70%)"], - ) - - date_and_location_width: LaTeXDimension = Field( - default="4.1 cm", - title="Date and Location Column Width", - description="The width of the date and location column.", - ) - - text_alignment: Literal["left-aligned", "justified"] = Field( - default="left-aligned", - title="Text Alignment", - description="The alignment of the text.", - ) - - show_timespan_in: list[str] = Field( - default=[], - title="Show Time Span in These Sections", - description=( - "The time span will be shown in the date and location column in these" - " sections. The input should be a list of strings." - ), - ) - - show_last_updated_date: bool = Field( - default=True, - title="Show Last Updated Date", - description=( - "If this option is set to true, then the last updated date will be shown" - " in the header." - ), - ) - - header_font_size: LaTeXDimension = Field( - default="30 pt", - title="Header Font Size", - description="The font size of the header (the name of the person).", - ) - - margins: ClassicThemeMargins = Field( - default=ClassicThemeMargins(), - title="Margins", - description="Page, section title, entry field, and highlights field margins.", - ) - - -class Design(BaseModel): - """This class stores the theme name of the CV and the theme's options.""" - - theme: Literal["classic"] = Field( - default="classic", - title="Theme name", - description='The only option is "Classic" for now.', - ) - font: Literal["SourceSans3", "Roboto", "EBGaramond"] = Field( - default="SourceSans3", - title="Font", - description="The font of the CV.", - ) - font_size: Literal["10pt", "11pt", "12pt"] = Field( - default="10pt", - title="Font Size", - description="The font size of the CV. It can be 10pt, 11pt, or 12pt.", - ) - page_size: Literal["a4paper", "letterpaper"] = Field( - default="a4paper", - title="Page Size", - description="The page size of the CV. It can be a4paper or letterpaper.", - ) - options: Optional[ClassicThemeOptions] = Field( - default=None, - title="Theme Options", - description="The options of the theme.", - ) - - @model_validator(mode="after") - @classmethod - def check_theme_options(cls, model): - """Check if the correct options are provided for the theme. If the theme - options are not provided, then set the default options for the theme. - """ - if model.options is None: - if model.theme == "classic": - model.options = ClassicThemeOptions() - else: - raise RuntimeError(f'The theme "{model.theme}" does not exist.') - else: - if model.theme == "classic": - if not isinstance(model.options, ClassicThemeOptions): - raise ValueError( - "Theme is classic but options is not classic theme options." - ) - else: - raise RuntimeError(f'The theme "{model.theme}"" does not exist.') - - return model - - @field_validator("font") - @classmethod - def check_font(cls, font: str) -> str: - """Go to the fonts directory and check if the font exists. If it exists, then - check if all the required files are there. - """ - fonts_directory = str(files("rendercv").joinpath("templates", "fonts")) - if font not in os.listdir(fonts_directory): - raise ValueError( - f'The font "{font}" is not found in the "fonts" directory.' - ) - else: - font_directory = os.path.join(fonts_directory, font) - required_files = [ - f"{font}-Bold.ttf", - f"{font}-BoldItalic.ttf", - f"{font}-Italic.ttf", - f"{font}-Regular.ttf", - ] - for file in required_files: - if file not in os.listdir(font_directory): - raise ValueError(f"{file} is not found in the {font} directory.") - - return font - - @field_validator("theme") - @classmethod - def check_if_theme_exists(cls, theme: str) -> str: - """Check if the theme exists in the templates directory.""" - template_directory = str(files("rendercv").joinpath("templates", theme)) - if f"{theme}.tex.j2" not in os.listdir(template_directory): - raise ValueError( - f'The theme "{theme}" is not found in the "templates" directory.' - ) - - return theme - - -# ====================================================================================== -# ====================================================================================== -# ====================================================================================== - -# ====================================================================================== -# CONTENT MODELS ======================================================================= -# ====================================================================================== - -LaTeXString = Annotated[str, AfterValidator(escape_latex_characters)] -PastDate = Annotated[ - str, Field(pattern=r"\d{4}-?(\d{2})?-?(\d{2})?"), AfterValidator(parse_date_string) -] - - -class Event(BaseModel): - """This class is the parent class for classes like `#!python EducationEntry`, - `#!python ExperienceEntry`, `#!python NormalEntry`, and `#!python OneLineEntry`. - - It stores the common fields between these classes like dates, location, highlights, - and URL. - """ - - start_date: Optional[PastDate] = Field( - default=None, - title="Start Date", - description="The start date of the event in YYYY-MM-DD format.", - examples=["2020-09-24"], - ) - end_date: Optional[Literal["present"] | PastDate] = Field( - default=None, - title="End Date", - description=( - "The end date of the event in YYYY-MM-DD format. If the event is still" - ' ongoing, then the value should be "present".' - ), - examples=["2020-09-24", "present"], - ) - date: Optional[PastDate | LaTeXString] = Field( - default=None, - title="Date", - description=( - "If the event is a one-day event, then this field should be filled in" - " YYYY-MM-DD format. If the event is a multi-day event, then the start date" - " and end date should be provided instead. All of them can't be provided at" - " the same time." - ), - examples=["2020-09-24", "My Custom Date"], - ) - highlights: Optional[list[LaTeXString]] = Field( - default=[], - title="Highlights", - description=( - "The highlights of the event. It will be rendered as bullet points." - ), - examples=["Did this.", "Did that."], - ) - location: Optional[LaTeXString] = Field( - default=None, - title="Location", - description=( - "The location of the event. It will be shown with the date in the" - " same column." - ), - examples=["Istanbul, Turkey"], - ) - url: Optional[HttpUrl] = None - - @field_validator("date") - @classmethod - def check_date(cls, date: PastDate | LaTeXString) -> PastDate | LaTeXString: - """Check if the date is a string or a Date object and return accordingly.""" - if isinstance(date, str): - try: - # If this runs, it means the date is an ISO format string, and it can be - # parsed - date = parse_date_string(date) - except ValueError: - # Then it means it is a custom string like "Fall 2023" - date = date - - return date - - @model_validator(mode="after") - @classmethod - def check_dates(cls, model): - """Make sure that either `#!python start_date` and `#!python end_date` or only - `#!python date` is provided. - """ - date_is_provided = False - start_date_is_provided = False - end_date_is_provided = False - if model.date is not None: - date_is_provided = True - if model.start_date is not None: - start_date_is_provided = True - if model.end_date is not None: - end_date_is_provided = True - - if date_is_provided and start_date_is_provided and end_date_is_provided: - logger.warning( - '"start_date", "end_date" and "date" are all provided in of the' - " entries. Therefore, date will be ignored." - ) - model.date = None - - elif date_is_provided and start_date_is_provided and not end_date_is_provided: - logger.warning( - 'Both "date" and "start_date" is provided in of the entries.' - ' "start_date" will be ignored.' - ) - model.start_date = None - model.end_date = None - - elif date_is_provided and end_date_is_provided and not start_date_is_provided: - logger.warning( - 'Both "date" and "end_date" is provided in of the entries. "end_date"' - " will be ignored." - ) - model.start_date = None - model.end_date = None - - elif start_date_is_provided and not end_date_is_provided: - logger.warning( - '"start_date" is provided in of the entries, but "end_date" is not.' - ' "end_date" will be set to "present".' - ) - model.end_date = "present" - - if model.start_date is not None and model.end_date is not None: - if model.end_date == "present": - end_date = Date.today() - elif isinstance(model.end_date, int): - # Then it means user only provided the year, so convert it to a Date - # object with the first day of the year (just for the date comparison) - end_date = Date(model.end_date, 1, 1) - elif isinstance(model.end_date, Date): - # Then it means user provided either YYYY-MM-DD or YYYY-MM - end_date = model.end_date - else: - raise RuntimeError("end_date is neither an integer nor a Date object.") - - if isinstance(model.start_date, int): - # Then it means user only provided the year, so convert it to a Date - # object with the first day of the year (just for the date comparison) - start_date = Date(model.start_date, 1, 1) - elif isinstance(model.start_date, Date): - # Then it means user provided either YYYY-MM-DD or YYYY-MM - start_date = model.start_date - else: - raise RuntimeError( - "start_date is neither an integer nor a Date object." - ) - - if start_date > end_date: - raise ValueError( - '"start_date" can not be after "end_date". Please check the dates.' - ) - - return model - - @computed_field - @cached_property - def date_and_location_strings_with_timespan(self) -> list[LaTeXString]: - date_and_location_strings = [] - - if self.location is not None: - date_and_location_strings.append(self.location) - - if self.date is not None: - if isinstance(self.date, str): - date_and_location_strings.append(self.date) - elif isinstance(self.date, Date): - date_and_location_strings.append(format_date(self.date)) - else: - raise RuntimeError("Date is neither a string nor a Date object.") - elif self.start_date is not None and self.end_date is not None: - start_date = format_date(self.start_date) - - if self.end_date == "present": - end_date = "present" - - time_span_string = compute_time_span_string( - self.start_date, Date.today() - ) - else: - end_date = format_date(self.end_date) - - time_span_string = compute_time_span_string( - self.start_date, self.end_date - ) - - date_and_location_strings.append(f"{start_date} to {end_date}") - - date_and_location_strings.append(f"{time_span_string}") - - return date_and_location_strings - - @computed_field - @cached_property - def date_and_location_strings_without_timespan(self) -> list[LaTeXString]: - # use copy() to avoid modifying the original list - date_and_location_strings = self.date_and_location_strings_with_timespan.copy() - for string in date_and_location_strings: - if ( - "years" in string - or "months" in string - or "year" in string - or "month" in string - ): - date_and_location_strings.remove(string) - - return date_and_location_strings - - @computed_field - @cached_property - def highlight_strings(self) -> list[LaTeXString]: - highlight_strings = [] - if self.highlights is not None: - highlight_strings.extend(self.highlights) - - return highlight_strings - - @computed_field - @cached_property - def markdown_url(self) -> Optional[str]: - if self.url is None: - return None - else: - url = str(self.url) - - if "github" in url: - link_text = "view on GitHub" - elif "linkedin" in url: - link_text = "view on LinkedIn" - elif "instagram" in url: - link_text = "view on Instagram" - elif "youtube" in url: - link_text = "view on YouTube" - else: - link_text = "view on my website" - - markdown_url = f"[{link_text}]({url})" - - return markdown_url - - @computed_field - @cached_property - def month_and_year(self) -> Optional[LaTeXString]: - if self.date is not None: - # Then it means start_date and end_date are not provided. - try: - # If this runs, it means the date is an ISO format string, and it can be - # parsed - month_and_year = format_date(self.date) # type: ignore - except TypeError: - month_and_year = str(self.date) - else: - # Then it means start_date and end_date are provided and month_and_year - # doesn't make sense. - month_and_year = None - - return month_and_year - - -class OneLineEntry(Event): - """This class stores [OneLineEntry](../user_guide.md#onelineentry) information.""" - - name: LaTeXString = Field( - title="Name", - description="The name of the entry. It will be shown as bold text.", - ) - details: LaTeXString = Field( - title="Details", - description="The details of the entry. It will be shown as normal text.", - ) - - -class NormalEntry(Event): - """This class stores [NormalEntry](../user_guide.md#normalentry) information.""" - - name: LaTeXString = Field( - title="Name", - description="The name of the entry. It will be shown as bold text.", - ) - - -class ExperienceEntry(Event): - """This class stores [ExperienceEntry](../user_guide.md#experienceentry) information.""" - - company: LaTeXString = Field( - title="Company", - description="The company name. It will be shown as bold text.", - ) - position: LaTeXString = Field( - title="Position", - description="The position. It will be shown as normal text.", - ) - - -class EducationEntry(Event): - """This class stores [EducationEntry](../user_guide.md#educationentry) information.""" - - institution: LaTeXString = Field( - title="Institution", - description="The institution name. It will be shown as bold text.", - examples=["Bogazici University"], - ) - area: LaTeXString = Field( - title="Area", - description="The area of study. It will be shown as normal text.", - ) - study_type: Optional[LaTeXString] = Field( - default=None, - title="Study Type", - description="The type of the degree.", - examples=["BS", "BA", "PhD", "MS"], - ) - gpa: Optional[LaTeXString | float] = Field( - default=None, - title="GPA", - description="The GPA of the degree.", - ) - transcript_url: Optional[HttpUrl] = Field( - default=None, - title="Transcript URL", - description=( - "The URL of the transcript. It will be shown as a link next to the GPA." - ), - examples=["https://example.com/transcript.pdf"], - ) - - @computed_field - @cached_property - def highlight_strings(self) -> list[LaTeXString]: - highlight_strings = [] - - if self.gpa is not None: - gpaString = f"GPA: {self.gpa}" - if self.transcript_url is not None: - gpaString += f" ([Transcript]({self.transcript_url}))" - highlight_strings.append(gpaString) - - if self.highlights is not None: - highlight_strings.extend(self.highlights) - - return highlight_strings - - -class PublicationEntry(Event): - """This class stores [PublicationEntry](../user_guide.md#publicationentry) information.""" - - title: LaTeXString = Field( - title="Title of the Publication", - description="The title of the publication. It will be shown as bold text.", - ) - authors: list[LaTeXString] = Field( - title="Authors", - description="The authors of the publication in order as a list of strings.", - ) - doi: str = Field( - title="DOI", - description="The DOI of the publication.", - examples=["10.48550/arXiv.2310.03138"], - ) - date: LaTeXString = Field( - title="Publication Date", - description="The date of the publication.", - examples=["2021-10-31"], - ) - cited_by: Optional[int] = Field( - default=None, - title="Cited By", - description="The number of citations of the publication.", - ) - journal: Optional[LaTeXString] = Field( - default=None, - title="Journal", - description="The journal or the conference name.", - ) - - @field_validator("doi") - @classmethod - def check_doi(cls, doi: str) -> str: - """Check if the DOI exists in the DOI System.""" - doi_url = f"https://doi.org/{doi}" - - try: - urllib.request.urlopen(doi_url) - except urllib.request.HTTPError as err: - if err.code == 404: - raise ValueError(f"{doi} cannot be found in the DOI System.") - - return doi - - @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. - - Currently, only LinkedIn, Github, and Instagram are supported. - """ - - network: Literal["LinkedIn", "GitHub", "Instagram", "Orcid"] = Field( - title="Social Network", - description="The social network name.", - ) - username: str = Field( - title="Username", - description="The username of the social network. The link will be generated.", - ) - - -class Connection(BaseModel): - """This class stores a connection/communication information. - - Warning: - This class isn't designed for users to use, but it is used by RenderCV to make - the $\\LaTeX$ templating easier. - """ - - name: Literal[ - "LinkedIn", - "GitHub", - "Instagram", - "Orcid", - "phone", - "email", - "website", - "location", - ] - value: str - - @computed_field - @cached_property - def url(self) -> Optional[HttpUrl | str]: - if self.name == "LinkedIn": - url = f"https://www.linkedin.com/in/{self.value}" - elif self.name == "GitHub": - url = f"https://www.github.com/{self.value}" - elif self.name == "Instagram": - url = f"https://www.instagram.com/{self.value}" - elif self.name == "Orcid": - url = f"https://orcid.org/{self.value}" - elif self.name == "email": - url = f"mailto:{self.value}" - elif self.name == "website": - url = self.value - elif self.name == "phone": - url = self.value - elif self.name == "location": - url = None - else: - raise RuntimeError(f'"{self.name}" is not a valid connection.') - - return url - - -class SectionBase(BaseModel): - """This class stores a section information. - - It is the parent class of all the section classes like - `#!python SectionWithEducationEntries`, `#!python SectionWithExperienceEntries`, - `#!python SectionWithNormalEntries`, `#!python SectionWithOneLineEntries`, and - `#!python SectionWithPublicationEntries`. - """ - - title: LaTeXString = Field( - title="Section Title", - description="The title of the section.", - examples=["My Custom Section"], - ) - link_text: Optional[LaTeXString] = Field( - default=None, - title="Link Text", - description=( - "If the section has a link, then what should be the text of the link? If" - " this field is not provided, then the link text will be generated" - " automatically based on the URL." - ), - examples=["view on GitHub", "view on LinkedIn"], - ) - - @field_validator("title") - @classmethod - def make_first_letters_uppercase(cls, title: LaTeXString) -> LaTeXString: - """Capitalize the first letters of the words in the title.""" - return title.title() - - -entry_type_field = Field( - title="Entry Type", - description="The type of the entries in the section.", -) -entries_field = Field( - title="Entries", - description="The entries of the section. The format depends on the entry type.", -) - - -class SectionWithEducationEntries(SectionBase): - """This class stores a section with - [EducationEntry](../user_guide.md#educationentry)s. - """ - - entry_type: Literal["EducationEntry"] = entry_type_field - entries: list[EducationEntry] = entries_field - - -class SectionWithExperienceEntries(SectionBase): - """This class stores a section with - [ExperienceEntry](../user_guide.md#experienceentry)s. - """ - - entry_type: Literal["ExperienceEntry"] = entry_type_field - entries: list[ExperienceEntry] = entries_field - - -class SectionWithNormalEntries(SectionBase): - """This class stores a section with - [NormalEntry](../user_guide.md#normalentry)s. - """ - - entry_type: Literal["NormalEntry"] = entry_type_field - entries: list[NormalEntry] = entries_field - - -class SectionWithOneLineEntries(SectionBase): - """This class stores a section with - [OneLineEntry](../user_guide.md#onelineentry)s. - """ - - entry_type: Literal["OneLineEntry"] = entry_type_field - entries: list[OneLineEntry] = entries_field - - -class SectionWithPublicationEntries(SectionBase): - """This class stores a section with - [PublicationEntry](../user_guide.md#publicationentry)s. - """ - - entry_type: Literal["PublicationEntry"] = entry_type_field - entries: list[PublicationEntry] = entries_field - - -Section = Annotated[ - SectionWithEducationEntries - | SectionWithExperienceEntries - | SectionWithNormalEntries - | SectionWithOneLineEntries - | SectionWithPublicationEntries, - Field( - discriminator="entry_type", - ), -] - - -class CurriculumVitae(BaseModel): - """This class bindes all the information of a CV together.""" - - name: LaTeXString = Field( - title="Name", - description="The name of the person.", - ) - label: Optional[LaTeXString] = Field( - default=None, - title="Label", - description="The label of the person.", - ) - location: Optional[LaTeXString] = Field( - default=None, - title="Location", - description="The location of the person. This is not rendered currently.", - ) - email: Optional[EmailStr] = Field( - default=None, - title="Email", - description="The email of the person. It will be rendered in the heading.", - ) - phone: Optional[PhoneNumber] = None - website: Optional[HttpUrl] = None - social_networks: Optional[list[SocialNetwork]] = Field( - default=None, - title="Social Networks", - description=( - "The social networks of the person. They will be rendered in the heading." - ), - ) - summary: Optional[LaTeXString] = Field( - default=None, - title="Summary", - description="The summary of the person.", - ) - # Sections: - section_order: Optional[list[str]] = Field( - default=None, - title="Section Order", - description=( - "The order of sections in the CV. The section title should be used." - ), - ) - education: Optional[list[EducationEntry]] = Field( - default=None, - title="Education", - description="The education entries of the person.", - ) - experience: Optional[list[ExperienceEntry]] = Field( - default=None, - title="Experience", - description="The experience entries of the person.", - ) - work_experience: Optional[list[ExperienceEntry]] = Field( - default=None, - title="Work Experience", - description="The work experience entries of the person.", - ) - projects: Optional[list[NormalEntry]] = Field( - default=None, - title="Projects", - description="The project entries of the person.", - ) - academic_projects: Optional[list[NormalEntry]] = Field( - default=None, - title="Academic Projects", - description="The academic project entries of the person.", - ) - university_projects: Optional[list[NormalEntry]] = Field( - default=None, - title="University Projects", - description="The university project entries of the person.", - ) - personal_projects: Optional[list[NormalEntry]] = Field( - default=None, - 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", - description="The certificate entries of the person.", - ) - extracurricular_activities: Optional[list[ExperienceEntry]] = Field( - default=None, - title="Extracurricular Activities", - description="The extracurricular activity entries of the person.", - ) - test_scores: Optional[list[OneLineEntry]] = Field( - default=None, - title="Test Scores", - description="The test score entries of the person.", - ) - programming_skills: Optional[list[NormalEntry]] = Field( - default=None, - title="Programming Skills", - description="The programming skill entries of the person.", - ) - skills: Optional[list[OneLineEntry]] = Field( - default=None, - title="Skills", - description="The skill entries of the person.", - ) - other_skills: Optional[list[OneLineEntry]] = Field( - default=None, - title="Skills", - description="The skill entries of the person.", - ) - awards: Optional[list[OneLineEntry]] = Field( - default=None, - title="Awards", - description="The award entries of the person.", - ) - interests: Optional[list[OneLineEntry]] = Field( - default=None, - title="Interests", - description="The interest entries of the person.", - ) - custom_sections: Optional[list[Section]] = Field( - default=None, - title="Custom Sections", - description=( - "Custom sections with custom section titles can be rendered as well." - ), - ) - - @model_validator(mode="after") - @classmethod - def check_if_the_section_names_are_unique(cls, model): - """Check if the section names are unique.""" - pre_defined_section_names = [ - "Education", - "Work Experience", - "Academic Projects", - "Personal Projects", - "Certificates", - "Extracurricular Activities", - "Test Scores", - "Skills", - "Publications", - ] - if model.custom_sections is not None: - custom_section_names = [] - for custom_section in model.custom_sections: - custom_section_names.append(custom_section.title) - - section_names = pre_defined_section_names + custom_section_names - else: - section_names = pre_defined_section_names - - seen = set() - duplicates = {val for val in section_names if (val in seen or seen.add(val))} - if len(duplicates) > 0: - raise ValueError( - "The section names should be unique. The following section names are" - f" duplicated: {duplicates}" - ) - - return model - - @computed_field - @cached_property - def connections(self) -> list[Connection]: - connections = [] - if self.location is not None: - connections.append(Connection(name="location", value=self.location)) - if self.phone is not None: - connections.append(Connection(name="phone", value=self.phone)) - if self.email is not None: - connections.append(Connection(name="email", value=self.email)) - if self.website is not None: - connections.append(Connection(name="website", value=str(self.website))) - if self.social_networks is not None: - for social_network in self.social_networks: - connections.append( - Connection( - name=social_network.network, value=social_network.username - ) - ) - - return connections - - @computed_field - @cached_property - def sections(self) -> list[SectionBase]: - sections = [] - - # Pre-defined sections (i.e. sections that are not custom)): - pre_defined_sections = { - "Education": self.education, - "Experience": self.experience, - "Work Experience": self.work_experience, - "Publications": self.publications, - "Projects": self.projects, - "Academic Projects": self.academic_projects, - "University Projects": self.university_projects, - "Personal Projects": self.personal_projects, - "Certificates": self.certificates, - "Extracurricular Activities": self.extracurricular_activities, - "Test Scores": self.test_scores, - "Skills": self.skills, - "Programming Skills": self.programming_skills, - "Other Skills": self.other_skills, - "Awards": self.awards, - "Interests": self.interests, - "Programming Skills": self.programming_skills, - } - - section_order_is_given = True - if self.section_order is None: - section_order_is_given = False - # If the user didn't specify the section order, then use the default order: - self.section_order = list(pre_defined_sections.keys()) - if self.custom_sections is not None: - # If the user specified custom sections, then add them to the end of the - # section order with the same order as they are in the input file: - self.section_order.extend( - [section.title for section in self.custom_sections] - ) - - link_text = None - entry_type = None - entries = None - for section_name in self.section_order: - # Create a section for each section name in the section order: - if section_name in pre_defined_sections: - if pre_defined_sections[section_name] is None: - if section_order_is_given: - raise ValueError( - f'The section "{section_name}" is not found in the CV.' - " Please create the section or delete it from the section" - " order." - ) - else: - continue - - entry_type = pre_defined_sections[section_name][0].__class__.__name__ - entries = pre_defined_sections[section_name] - if section_name == "Test Scores": - link_text = "Score Report" - elif section_name == "Certificates": - link_text = "Certificate" - else: - link_text = None - else: - # If the section is not pre-defined, then it is a custom section. - # Find the corresponding custom section and get its entries: - for custom_section in self.custom_sections: # type: ignore - if custom_section.title == section_name: - entry_type = custom_section.entries[0].__class__.__name__ - link_text = custom_section.link_text - entries = custom_section.entries - break - else: - entry_type = None - link_text = None - entries = None - - if entry_type is None or entries is None: - raise ValueError( - f'"{section_name}" is not a valid section name. Please create a' - " custom section with this name or delete it from the section" - " order." - ) - - object_map = { - "EducationEntry": SectionWithEducationEntries, - "ExperienceEntry": SectionWithExperienceEntries, - "NormalEntry": SectionWithNormalEntries, - "OneLineEntry": SectionWithOneLineEntries, - "PublicationEntry": SectionWithPublicationEntries, - } - - section = object_map[entry_type]( - title=section_name, - entry_type=entry_type, # type: ignore - entries=entries, - link_text=link_text, - ) - sections.append(section) - - # Check if any of the pre-defined sections are missing from the section order: - for section_name in pre_defined_sections: - if pre_defined_sections[section_name] is not None: - if section_name not in self.section_order: - logger.warning( - f'The section "{section_name}" is not found in the section' - " order! It will not be rendered." - ) - - # Check if any of the custom sections are missing from the section order: - if self.custom_sections is not None: - for custom_section in self.custom_sections: - if custom_section.title not in self.section_order: - logger.warning( - f'The custom section "{custom_section.title}" is not found in' - " the section order! It will not be rendered." - ) - - return sections - - -# ====================================================================================== -# ====================================================================================== -# ====================================================================================== - - -class RenderCVDataModel(BaseModel): - """This class binds both the CV and the design information together.""" - - design: Design = Field( - default=Design(), - title="Design", - description="The design of the CV.", - ) - cv: CurriculumVitae = Field( - default=CurriculumVitae(name="John Doe"), - title="Curriculum Vitae", - description="The data of the CV.", - ) - - @model_validator(mode="after") - @classmethod - def check_classical_theme_show_timespan_in(cls, model): - """Check if the sections that are specified in the "show_timespan_in" option - exist in the CV. - """ - if model.design.theme == "classic": - design: Design = model.design - cv: CurriculumVitae = model.cv - section_titles = [section.title for section in cv.sections] - for title in design.options.show_timespan_in: # type: ignore - if title not in section_titles: - not_used_section_titles = list( - set(section_titles) - set(design.options.show_timespan_in) - ) - not_used_section_titles = ", ".join(not_used_section_titles) - raise ValueError( - f'The section "{title}" that is specified in the' - ' "show_timespan_in" option is not found in the CV. You' - " might have wanted to use one of these:" - f" {not_used_section_titles}." - ) - - return model - - -def read_input_file(file_path: str) -> RenderCVDataModel: - """Read the input file. - - Args: - file_path (str): The path to the input file. - - Returns: - str: The input file as a string. - """ - start_time = time.time() - logger.info(f"Reading and validating the input file {file_path} has started.") - - # check if the file exists: - if not os.path.exists(file_path): - raise FileNotFoundError(f"The file {file_path} doesn't exist.") - - # check the file extension: - accepted_extensions = [".yaml", ".yml", ".json", ".json5"] - if not any(file_path.endswith(extension) for extension in accepted_extensions): - raise ValueError( - f"The file {file_path} doesn't have an accepted extension!" - f" Accepted extensions are: {accepted_extensions}" - ) - - with open(file_path) as file: - yaml = YAML() - raw_json = yaml.load(file) - - data = RenderCVDataModel(**raw_json) - - end_time = time.time() - time_taken = end_time - start_time - logger.info( - f"Reading and validating the input file {file_path} has finished in" - f" {time_taken:.2f} s." - ) - return data diff --git a/rendercv/rendering.py b/rendercv/rendering.py deleted file mode 100644 index 9566b15..0000000 --- a/rendercv/rendering.py +++ /dev/null @@ -1,493 +0,0 @@ -"""This module implements LaTeX file generation and LaTeX runner utilities for RenderCV. -""" -import subprocess -import os -import re -import shutil -from datetime import date -import logging -import time -from typing import Optional -import sys -from importlib.resources import files - -from .data_model import RenderCVDataModel, CurriculumVitae, Design, ClassicThemeOptions - -from jinja2 import Environment, PackageLoader - -logger = logging.getLogger(__name__) - - -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).") - ``` - - will return: - - `#!pytjon "This is a \\textbf{bold} text with a \\href{https://google.com}{\\textit{link}}."` - - Args: - markdown_string (str): The markdown string to convert. - - Returns: - str: The LaTeX string. - """ - if not isinstance(markdown_string, str): - raise ValueError("markdown_to_latex should only be used on strings!") - - # convert links - links = re.findall(r"\[([^\]\[]*)\]\((.*?)\)", markdown_string) - if links is not None: - for link in links: - link_text = link[0] - link_url = link[1] - - old_link_string = f"[{link_text}]({link_url})" - new_link_string = "\\href{" + link_url + "}{" + link_text + "}" - - markdown_string = markdown_string.replace(old_link_string, new_link_string) - - # convert bold - bolds = re.findall(r"\*\*([^\*]*)\*\*", markdown_string) - if bolds is not None: - for bold_text in bolds: - old_bold_text = f"**{bold_text}**" - new_bold_text = "\\textbf{" + bold_text + "}" - - markdown_string = markdown_string.replace(old_bold_text, new_bold_text) - - # convert italic - italics = re.findall(r"\*([^\*]*)\*", markdown_string) - if italics is not None: - for italic_text in italics: - old_italic_text = f"*{italic_text}*" - new_italic_text = "\\textit{" + italic_text + "}" - - markdown_string = markdown_string.replace(old_italic_text, new_italic_text) - - latex_string = markdown_string - - return latex_string - - -def markdown_link_to_url(value: str) -> str: - """Convert a markdown link to a normal string URL. - - This function is used as a Jinja2 filter. - - Example: - ```python - markdown_link_to_url("[Google](https://google.com)") - ``` - - will return: - - `#!python "https://google.com"` - - Args: - value (str): The markdown link to convert. - - Returns: - str: The URL as a string. - """ - if not isinstance(value, str): - raise ValueError("markdown_to_latex should only be used on strings!") - - link = re.search(r"\[(.*)\]\((.*?)\)", value) - if link is not None: - url = link.groups()[1] - if url == "": - raise ValueError(f"The markdown link {value} is empty!") - return url - else: - raise ValueError("markdown_link_to_url should only be used on markdown links!") - - -def make_it_something( - value: str, something: str, match_str: Optional[str] = None -) -> str: - """Make the matched parts of the string something. If the match_str is None, the - whole string will be made something. - - Warning: - This function shouldn't be used directly. Use - [make_it_bold](rendering.md#rendercv.rendering.make_it_bold), - [make_it_underlined](rendering.md#rendercv.rendering.make_it_underlined), or - [make_it_italic](rendering.md#rendercv.rendering.make_it_italic) instead. - """ - if not isinstance(value, str): - raise ValueError(f"{something} should only be used on strings!") - - if match_str is not None and not isinstance(match_str, str): - raise ValueError("The string to match should be a string!") - - if match_str is None: - return f"\\{something}{{{value}}}" - - if match_str in value: - value = value.replace(match_str, f"\\{something}{{{match_str}}}") - return value - else: - return value - - -def make_it_bold(value: str, match_str: Optional[str] = None) -> str: - """Make the matched parts of the string bold. If the match_str is None, the whole - string will be made bold. - - This function is used as a Jinja2 filter. - - Example: - ```python - make_it_bold("Hello World!", "Hello") - ``` - - will return: - - `#!python "\\textbf{Hello} World!"` - - Args: - value (str): The string to make bold. - match_str (str): The string to match. - """ - return make_it_something(value, "textbf", match_str) - - -def make_it_underlined(value: str, match_str: Optional[str] = None) -> str: - """Make the matched parts of the string underlined. If the match_str is None, the - whole string will be made underlined. - - This function is used as a Jinja2 filter. - - Example: - ```python - make_it_underlined("Hello World!", "Hello") - ``` - - will return: - - `#!python "\\underline{Hello} World!"` - - Args: - value (str): The string to make underlined. - match_str (str): The string to match. - """ - return make_it_something(value, "underline", match_str) - - -def make_it_italic(value: str, match_str: Optional[str] = None) -> str: - """Make the matched parts of the string italic. If the match_str is None, the whole - string will be made italic. - - This function is used as a Jinja2 filter. - - Example: - ```python - make_it_italic("Hello World!", "Hello") - ``` - - will return: - - `#!python "\\textit{Hello} World!"` - - Args: - value (str): The string to make italic. - match_str (str): The string to match. - """ - return make_it_something(value, "textit", match_str) - - -def make_it_nolinebreak(value: str, match_str: Optional[str] = None) -> str: - """Make the matched parts of the string non line breakable. If the match_str is - None, the whole string will be made nonbreakable. - - This function is used as a Jinja2 filter. - - Example: - ```python - make_it_nolinebreak("Hello World!", "Hello") - ``` - - will return: - - `#!python "\\mbox{Hello} World!"` - - Args: - value (str): The string to disable line breaks. - match_str (str): The string to match. - """ - return make_it_something(value, "mbox", match_str) - - -def abbreviate_name(name: list[str]) -> str: - """Abbreviate a name by keeping the first letters of the first names. - - This function is used as a Jinja2 filter. - - Example: - ```python - abbreviate_name("John Doe") - ``` - - will return: - - `#!python "J. Doe"` - - Args: - name (str): The name to abbreviate. - Returns: - str: The abbreviated name. - """ - first_names = name.split(" ")[:-1] - first_names_initials = [first_name[0] + "." for first_name in first_names] - last_name = name.split(" ")[-1] - abbreviated_name = " ".join(first_names_initials) + " " + last_name - - return abbreviated_name - - -def divide_length_by(length: str, divider: float) -> str: - r"""Divide a length by a number. - - Length is a string with the following regex pattern: `\d+\.?\d* *(cm|in|pt|mm|ex|em)` - """ - # Get the value as a float and the unit as a string: - value = re.search(r"\d+\.?\d*", length).group() # type: ignore - unit = re.findall(r"[^\d\.\s]+", length)[0] - - return str(float(value) / divider) + " " + unit - - -def get_today() -> str: - """Return today's date in the format of "Month Year". - - Returns: - str: Today's date. - """ - - today = date.today() - return today.strftime("%B %Y") - - -def get_path_to_font_directory(font_name: str) -> str: - """Return the path to the fonts directory. - - Returns: - str: The path to the fonts directory. - """ - return str(files("rendercv").joinpath("templates", "fonts", font_name)) - - -def render_template(data: RenderCVDataModel, output_path: Optional[str] = None) -> str: - """Render the template using the given data. - - Args: - data (RenderCVDataModel): The data to use to render the template. - - Returns: - str: The path to the rendered LaTeX file. - """ - start_time = time.time() - logger.info("Rendering the LaTeX file has started.") - - # create a Jinja2 environment: - theme = data.design.theme - environment = Environment( - loader=PackageLoader("rendercv", os.path.join("templates", theme)), - trim_blocks=True, - lstrip_blocks=True, - ) - - # add new functions to the environment: - environment.globals.update(str=str) - - # set custom delimiters for LaTeX templating: - environment.block_start_string = "((*" - environment.block_end_string = "*))" - environment.variable_start_string = "<<" - environment.variable_end_string = ">>" - environment.comment_start_string = "((#" - environment.comment_end_string = "#))" - - # add custom filters: - environment.filters["markdown_to_latex"] = markdown_to_latex - environment.filters["markdown_link_to_url"] = markdown_link_to_url - environment.filters["make_it_bold"] = make_it_bold - environment.filters["make_it_underlined"] = make_it_underlined - environment.filters["make_it_italic"] = make_it_italic - environment.filters["make_it_nolinebreak"] = make_it_nolinebreak - environment.filters["make_it_something"] = make_it_something - environment.filters["divide_length_by"] = divide_length_by - environment.filters["abbreviate_name"] = abbreviate_name - - # load the template: - template = environment.get_template(f"{theme}.tex.j2") - - cv: CurriculumVitae = data.cv - design: Design = data.design - theme_options: ClassicThemeOptions = data.design.options - output_latex_file = template.render( - cv=cv, - design=design, - theme_options=theme_options, - today=get_today(), - ) - - # Create an output file and write the rendered LaTeX code to it: - if output_path is None: - output_path = os.getcwd() - - output_folder = os.path.join(output_path, "output") - file_name = data.cv.name.replace(" ", "_") + "_CV.tex" - output_file_path = os.path.join(output_folder, file_name) - os.makedirs(os.path.dirname(output_file_path), exist_ok=True) - with open(output_file_path, "w") as file: - file.write(output_latex_file) - - # Copy the fonts directory to the output directory: - # Remove the old fonts directory if it exists: - if os.path.exists(os.path.join(os.path.dirname(output_file_path), "fonts")): - shutil.rmtree(os.path.join(os.path.dirname(output_file_path), "fonts")) - - font_directory = get_path_to_font_directory(data.design.font) - output_fonts_directory = os.path.join(os.path.dirname(output_file_path), "fonts") - shutil.copytree( - font_directory, - output_fonts_directory, - dirs_exist_ok=True, - ) - - # Copy auxiliary files to the output directory (if there is any): - output_directory = os.path.dirname(output_file_path) - theme_directory = str(files("rendercv").joinpath("templates", theme)) - for file_name in os.listdir(theme_directory): - if file_name.endswith(".cls"): - shutil.copy( - os.path.join(theme_directory, file_name), - output_directory, - ) - - end_time = time.time() - time_taken = end_time - start_time - logger.info( - f"Rendering the LaTeX file ({output_file_path}) has finished in" - f" {time_taken:.2f} s." - ) - - return output_file_path - - -def run_latex(latex_file_path: str) -> str: - """ - Run TinyTeX with the given LaTeX file and generate a PDF. - - Args: - latex_file_path (str): The path to the LaTeX file to compile. - """ - start_time = time.time() - logger.info("Running TinyTeX to generate the PDF has started.") - latex_file_name = os.path.basename(latex_file_path) - latex_file_path = os.path.normpath(latex_file_path) - - # check if the file exists: - if not os.path.exists(latex_file_path): - raise FileNotFoundError(f"The file {latex_file_path} doesn't exist!") - - output_file_name = latex_file_name.replace(".tex", ".pdf") - output_file_path = os.path.join(os.path.dirname(latex_file_path), output_file_name) - - if sys.platform == "win32": - # Windows - executable = str( - files("rendercv").joinpath( - "vendor", "TinyTeX", "bin", "windows", "lualatex.exe" - ) - ) - - elif sys.platform == "linux" or sys.platform == "linux2": - # Linux - executable = str( - files("rendercv").joinpath( - "vendor", "TinyTeX", "bin", "x86_64-linux", "lualatex" - ) - ) - elif sys.platform == "darwin": - # MacOS - executable = str( - files("rendercv").joinpath( - "vendor", "TinyTeX", "bin", "universal-darwin", "lualatex" - ) - ) - else: - raise OSError(f"Unknown OS {os.name}!") - - # Check if the executable exists: - if not os.path.exists(executable): - raise FileNotFoundError( - f"The TinyTeX executable ({executable}) doesn't exist! Please install" - " RenderCV again." - ) - - # Run TinyTeX: - def run(): - with subprocess.Popen( - [ - executable, - f"{latex_file_name}", - ], - cwd=os.path.dirname(latex_file_path), - stdout=subprocess.PIPE, - stdin=subprocess.DEVNULL, # don't allow TinyTeX to ask for user input - text=True, - encoding="utf-8", - ) as latex_process: - output, error = latex_process.communicate() - - if latex_process.returncode != 0: - # Find the error line: - for line in output.split("\n"): - if line.startswith("! "): - error_line = line.replace("! ", "") - break - - raise RuntimeError( - "Running TinyTeX has failed with the following error:", - f"{error_line}", - "If you can't solve the problem, please try to re-install RenderCV," - " or open an issue on GitHub.", - ) - - run() - run() # run twice for cross-references - - # check if the PDF file is generated: - if not os.path.exists(output_file_path): - raise FileNotFoundError( - f"The PDF file {output_file_path} couldn't be generated! If you can't" - " solve the problem, please try to re-install RenderCV, or open an issue" - " on GitHub." - ) - - # remove the unnecessary files: - for file_name in os.listdir(os.path.dirname(latex_file_path)): - if ( - file_name.endswith(".aux") - or file_name.endswith(".log") - or file_name.endswith(".out") - ): - os.remove(os.path.join(os.path.dirname(latex_file_path), file_name)) - - end_time = time.time() - time_taken = end_time - start_time - logger.info( - f"Running TinyTeX to generate the PDF ({output_file_path}) has finished in" - f" {time_taken:.2f} s." - ) - - return output_file_path diff --git a/rendercv/templates/classic/ActivityEntry.j2.tex b/rendercv/templates/classic/ActivityEntry.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/EducationEntry.j2.tex b/rendercv/templates/classic/EducationEntry.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/Header.j2.tex b/rendercv/templates/classic/Header.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/OneLineEntry.j2.tex b/rendercv/templates/classic/OneLineEntry.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/Preamble.j2.tex b/rendercv/templates/classic/Preamble.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/PublicationEntry.j2.tex b/rendercv/templates/classic/PublicationEntry.j2.tex new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/classic/classic.tex.j2 b/rendercv/templates/classic/classic.tex.j2 deleted file mode 100644 index 2afbd50..0000000 --- a/rendercv/templates/classic/classic.tex.j2 +++ /dev/null @@ -1,163 +0,0 @@ -((# IMPORT MACROS #)) -((* from "components/section_contents.tex.j2" import section_contents with context *)) -((* from "components/header.tex.j2" import header with context *)) - -\documentclass[<>, <>]{article} - -% Packages: -\usepackage[ - ignoreheadfoot, % set margins without considering header and footer - top=<>, % seperation between body and page edge from the top - bottom=<>, % seperation between body and page edge from the bottom - left=<>, % seperation between body and page edge from the left - right=<>, % seperation between body and page edge from the right - footskip=<>, % seperation between body and footer - % showframe % for debugging - ]{geometry} % for adjusting page geometry -\usepackage{fontspec} % for loading fonts -\usepackage[explicit]{titlesec} % for customizing section titles -\usepackage{tabularx} % for making tables with fixed width columns -\usepackage{array} % tabularx requires this -\usepackage[dvipsnames]{xcolor} % for coloring text -\definecolor{primaryColor}{RGB}{<>} % define primary color -\usepackage{enumitem} % for customizing lists -\usepackage{fontawesome5} % for using icons -\usepackage[ - pdftitle={<>'s CV}, - pdfauthor={<>}, - colorlinks=true, - urlcolor=primaryColor -]{hyperref} % for links, metadata and bookmarks -\usepackage[pscoord]{eso-pic} % for floating text on the page -\usepackage{calc} % for calculating lengths -\usepackage{bookmark} % for bookmarks -\usepackage{lastpage} % for getting the total number of pages - -% Some settings: -\pagestyle{empty} % no header or footer -\setcounter{secnumdepth}{0} % no section numbering -\setlength{\parindent}{0pt} % no indentation -\setlength{\topskip}{0pt} % no top skip -((# \pagenumbering{gobble} % no page numbering #)) -\makeatletter -\let\ps@customFooterStyle\ps@plain % Copy the plain style to customFooterStyle -\patchcmd{\ps@customFooterStyle}{\thepage}{ - \color{gray}\textit{\small <> | Page \thepage{} of \pageref*{LastPage}} -}{}{} % replace number by desired string -\makeatother -\pagestyle{customFooterStyle} - -\setmainfont{<>}[ - Path= fonts/, - Extension = .ttf, - UprightFont = *-Regular, - ItalicFont = *-Italic, - BoldFont = *-Bold, - BoldItalicFont = *-BoldItalic -] - -\titleformat{\section}{ - % make the font size of the section title large and color it with the primary color - \Large\color{primaryColor} - }{ - }{ - }{ - % print bold title, give 0.15 cm space and draw a line of 0.8 pt thickness - % from the end of the title to the end of the body - \textbf{#1}\hspace{0.15cm}\titlerule[0.8pt]\hspace{-0.1cm} - }[] % section title formatting - -\titlespacing{\section}{ - % left space: - 0pt - }{ - % top space: - <> - }{ - % bottom space: - <> - } % section title spacing - -\newcolumntype{L}[1]{ - >{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}p{#1} -} % left-aligned fixed width column type -\newcolumntype{R}[1]{ - >{\raggedleft\let\newline\\\arraybackslash\hspace{0pt}}p{#1} -} % right-aligned fixed width column type -((* if theme_options.text_alignment == "justified" *)) -\newcolumntype{K}[1]{ - >{\let\newline\\\arraybackslash\hspace{0pt}}X -} % justified flexible width column type -((* elif theme_options.text_alignment == "left-aligned" *)) -\newcolumntype{K}[1]{ - >{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}X -} % left-aligned flexible width column type -((* endif *)) -\setlength\tabcolsep{-1.5pt} % no space between columns -\newenvironment{highlights}{ - \begin{itemize}[ - topsep=0pt, - parsep=<>, - partopsep=0pt, - itemsep=0pt, - after=\vspace{-1\baselineskip}, - leftmargin=<> + 3pt - ] - }{ - \end{itemize} - } % new environment for highlights - -\newenvironment{header}{ - \setlength{\topsep}{0pt}\par\kern\topsep\centering\color{primaryColor}\linespread{1.5} - }{ - \par\kern\topsep - } % new environment for the header - -\newcommand{\placelastupdatedtext}{% \placetextbox{}{}{} - \AddToShipoutPictureFG*{% Add to current page foreground - \put( - \LenToUnit{\paperwidth-<>-<>+0.05cm}, - \LenToUnit{\paperheight-<>} - ){\vtop{{\null}\makebox[0pt][c]{ - \small\color{gray}\textit{Last updated in <>}\hspace{\widthof{Last updated in <>}} - }}}% - }% -}% - -% save the original href command in a new command: -\let\hrefWithoutArrow\href - % new command for external links: -\renewcommand{\href}[2]{\hrefWithoutArrow{#1}{#2 \raisebox{.15ex}{\footnotesize \faExternalLink*}}} - -\begin{document} -((* if theme_options.show_last_updated_date *)) - \placelastupdatedtext -((* endif *)) - - <> - -((* if cv.summary is not none *)) - \section{Summary} - { - ((* if theme_options.text_alignment == "left-aligned" *)) - \raggedright - ((* endif *)) - \setlength{\leftskip}{<>} - \setlength{\rightskip}{<>} - - <> - - \setlength{\leftskip}{0cm} - \setlength{\rightskip}{0cm} - } -((* endif *)) - - \centering -((* for section in cv.sections *)) - \section{<>} - - <> - -((* endfor *)) - -\end{document} \ No newline at end of file diff --git a/rendercv/templates/classic/components/date_and_location_strings.tex.j2 b/rendercv/templates/classic/components/date_and_location_strings.tex.j2 deleted file mode 100644 index 62fecf3..0000000 --- a/rendercv/templates/classic/components/date_and_location_strings.tex.j2 +++ /dev/null @@ -1,9 +0,0 @@ -((* macro date_and_location_strings(date_and_location_strings) *)) - ((* for item in date_and_location_strings *)) - ((* if loop.last *)) -<> - ((* else *)) -<> \newline - ((* endif *)) - ((* endfor *)) -((* endmacro *)) \ No newline at end of file diff --git a/rendercv/templates/classic/components/entry.tex.j2 b/rendercv/templates/classic/components/entry.tex.j2 deleted file mode 100644 index 6aea6b8..0000000 --- a/rendercv/templates/classic/components/entry.tex.j2 +++ /dev/null @@ -1,114 +0,0 @@ -((* from "components/highlights.tex.j2" import highlights as print_higlights with context *)) -((* from "components/date_and_location_strings.tex.j2" import date_and_location_strings as print_date_and_locations with context *)) - -((* macro education(study_type, institution, area, highlights, date_and_location_strings)*)) -((# \begin{tabularx}{⟨width⟩}[⟨pos⟩]{⟨preamble⟩} #)) - ((# width: \textwidth #)) - ((# preamble: first column, second column, third column #)) - ((# first column: p{0.55cm}; constant width, ragged left column #)) - ((# second column: K{<>}; variable width, justified column #)) - ((# third column: R{<>}; constant widthm ragged right column #)) -\begin{tabularx}{\textwidth-<>-0.13cm}{L{0.85cm} K{<>} R{<>}} - \textbf{<>} - & - \textbf{<>}, <> - <> - & - <> -\end{tabularx} -((* endmacro *)) - -((* macro experience(company, position, highlights, date_and_location_strings)*)) -((# \begin{tabularx}{⟨width⟩}[⟨pos⟩]{⟨preamble⟩} #)) - ((# width: \textwidth #)) - ((# preamble: first column, second column #)) - ((# first column:: K{<>}; variable width, justified column #)) - ((# second column: R{<>}; constant width ragged right column #)) -\begin{tabularx}{\textwidth-<>-0.13cm}{K{<>} R{<>}} - \textbf{<>}, <> - <> - & - <> -\end{tabularx} -((* endmacro *)) - -((* macro normal(name, highlights, date_and_location_strings, markdown_url=none, link_text=none)*)) -((# \begin{tabularx}{⟨width⟩}[⟨pos⟩]{⟨preamble⟩} #)) - ((# width: \textwidth #)) - ((# preamble: first column, second column #)) - ((# first column:: K{<>}; variable width, justified column #)) - ((# second column: R{<>}; constant width ragged right column #)) - ((* if date_and_location_strings == [] *)) -\begin{tabularx}{\textwidth-<>-0.13cm}{K{<>}} - ((* if markdown_url is not none *)) - ((* if link_text is not none *)) - ((* set markdown_url = "["+link_text+"]("+ markdown_url|markdown_link_to_url +")" *)) - \textbf{<>}, <> - ((* else *)) - \textbf{<>}, <> - ((* endif *)) - ((* else *)) - \textbf{<>} - ((* endif *)) - <> -\end{tabularx} - ((* else *)) -\begin{tabularx}{\textwidth-<>-0.13cm}{K{<>} R{<>}} - ((* if markdown_url is not none *)) - ((* if link_text is not none *)) - ((* set markdown_url = "["+link_text+"]("+ markdown_url|markdown_link_to_url +")" *)) - \textbf{<>}, <> - ((* else *)) - \textbf{<>}, <> - ((* endif *)) - ((* else *)) - \textbf{<>} - ((* endif *)) - <> - & - <> -\end{tabularx} - ((* endif *)) -((* endmacro *)) - -((* macro publication(title, authors, journal, date, doi, doi_url)*)) -((# \begin{tabularx}{⟨width⟩}[⟨pos⟩]{⟨preamble⟩} #)) - ((# width: \textwidth #)) - ((# preamble: first column, second column #)) - ((# first column:: K{<>}; variable width, justified column #)) - ((# second column: R{<>}; constant width ragged right column #)) -\begin{tabularx}{\textwidth-<>-0.13cm}{K{<>} R{<>}} - \textbf{<>} - - \vspace{<<theme_options.margins.highlights_area.vertical_between_bullet_points>>} - - <<authors|map("abbreviate_name")|map("make_it_nolinebreak")|join(", ")|make_it_bold(cv.name|abbreviate_name)|make_it_italic(cv.name|abbreviate_name)>> - - \vspace{<<theme_options.margins.highlights_area.vertical_between_bullet_points>>} - - \href{<<doi_url>>}{<<doi>>} (<<journal>>) - & - <<date>> - -\end{tabularx} -((* endmacro *)) - -((* macro one_line(name, details, markdown_url=none, link_text=none) *)) - \begingroup((* if theme_options.text_alignment == "left-aligned" *))\raggedright((* endif *)) - \leftskip=<<theme_options.margins.entry_area.left_and_right>> - \advance\csname @rightskip\endcsname <<theme_options.margins.entry_area.left_and_right>> - \advance\rightskip <<theme_options.margins.entry_area.left_and_right>> - - ((* if markdown_url is not none *)) - ((* if link_text is not none *)) - ((* set markdown_url = "["+link_text+"]("+ markdown_url|markdown_link_to_url +")" *)) - \textbf{<<name|markdown_to_latex>>:} <<details|markdown_to_latex>> (<<markdown_url|markdown_to_latex>>) - ((* else *)) - \textbf{<<name|markdown_to_latex>>:} <<details|markdown_to_latex>> (<<markdown_url|markdown_to_latex>>) - ((* endif *)) - ((* else *)) - \textbf{<<name|markdown_to_latex>>:} <<details|markdown_to_latex>> - ((* endif *)) - - \par\endgroup -((* endmacro *)) \ No newline at end of file diff --git a/rendercv/templates/classic/components/header.tex.j2 b/rendercv/templates/classic/components/header.tex.j2 deleted file mode 100644 index dd7809c..0000000 --- a/rendercv/templates/classic/components/header.tex.j2 +++ /dev/null @@ -1,19 +0,0 @@ -((* import "components/header_connections.tex.j2" as print_connections *)) -((* macro header(name, connections) *)) -\begin{header} - \fontsize{<<theme_options.header_font_size>>}{<<theme_options.header_font_size>>} - \textbf{<<name>>} - - \vspace{<<theme_options.margins.header.vertical_between_name_and_connections>>} - - \normalsize -((* for connection in connections *)) - <<print_connections[connection.name|replace(" ", "")](connection.value, connection.url)>> - ((* if not loop.last *)) - \hspace{0.5cm} - ((* endif *)) -((* endfor *)) -\end{header} - -\vspace{<<theme_options.margins.header.bottom>>} -((* endmacro *)) \ No newline at end of file diff --git a/rendercv/templates/classic/components/header_connections.tex.j2 b/rendercv/templates/classic/components/header_connections.tex.j2 deleted file mode 100644 index a8e3f74..0000000 --- a/rendercv/templates/classic/components/header_connections.tex.j2 +++ /dev/null @@ -1,32 +0,0 @@ -((# Each macro in here is a link with an icon for header. #)) -((* macro LinkedIn(username, url) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faLinkedinIn}\hspace{0.13cm}<<username>>}} -((*- endmacro *)) - -((* macro GitHub(username, url) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faGithub}\hspace{0.13cm}<<username>>}} -((*- endmacro *)) - -((* macro Instagram(username, url) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faInstagram}\hspace{0.13cm}<<username>>}} -((*- endmacro *)) - -((* macro Orcid(username, url) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faOrcid}\hspace{0.13cm}<<username>>}} -((*- endmacro *)) - -((* macro phone(number, url) -*)) -\mbox{\hrefWithoutArrow{<<url|replace("-","")>>}{{\footnotesize\faPhone*}\hspace{0.13cm}<<number|replace("tel:", "")|replace("-"," ")>>}} -((*- endmacro *)) - -((* macro email(email, url) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faEnvelope[regular]}\hspace{0.13cm}<<email>>}} -((*- endmacro *)) - -((* macro website(url, dummy) -*)) -\mbox{\hrefWithoutArrow{<<url>>}{{\small\faLink}\hspace{0.13cm}<<url|replace("https://","")|replace("/","")>>}} -((*- endmacro *)) - -((* macro location(location, url) -*)) -\mbox{{\small\faMapMarker*}\hspace{0.13cm}<<location>>} -((*- endmacro *)) diff --git a/rendercv/templates/classic/components/highlights.tex.j2 b/rendercv/templates/classic/components/highlights.tex.j2 deleted file mode 100644 index 7bab5d1..0000000 --- a/rendercv/templates/classic/components/highlights.tex.j2 +++ /dev/null @@ -1,13 +0,0 @@ -((* macro highlights(highlights) *)) -\vspace{<<theme_options.margins.highlights_area.top>>} - ((* for item in highlights *)) - ((* if loop.first *)) -\begin{highlights} - ((* endif *)) - \item <<item|markdown_to_latex>> ((* if loop.last *))\hspace*{-0.2cm}((* endif *)) - - ((* if loop.last *)) -\end{highlights} - ((* endif *)) -((* endfor *)) -((* endmacro *)) \ No newline at end of file diff --git a/rendercv/templates/classic/components/section_contents.tex.j2 b/rendercv/templates/classic/components/section_contents.tex.j2 deleted file mode 100644 index 0af0c85..0000000 --- a/rendercv/templates/classic/components/section_contents.tex.j2 +++ /dev/null @@ -1,54 +0,0 @@ -((* import "components/entry.tex.j2" as entry with context *)) - -((* macro section_contents(title, entries, entry_type, link_text=none)*)) - ((* for value in entries *)) - ((* if title in theme_options.show_timespan_in *)) - ((* set date_and_location_strings = value.date_and_location_strings_with_timespan *)) - ((* else *)) - ((* set date_and_location_strings = value.date_and_location_strings_without_timespan *)) - ((* endif *)) - ((* if not loop.first *)) - \vspace{<<theme_options.margins.entry_area.vertical_between>>} - ((* endif *)) - ((* if entry_type == "EducationEntry" *)) - <<entry["education"]( - study_type=value.study_type, - institution=value.institution, - area=value.area, - highlights=value.highlight_strings, - date_and_location_strings=date_and_location_strings - )|indent(4)>> - ((* elif entry_type == "ExperienceEntry" *)) - <<entry["experience"]( - company=value.company, - position=value.position, - highlights=value.highlight_strings, - date_and_location_strings=date_and_location_strings - )|indent(4)>> - ((* elif entry_type == "NormalEntry" *)) - <<entry["normal"]( - name=value.name, - highlights=value.highlight_strings, - date_and_location_strings=date_and_location_strings, - markdown_url=value.markdown_url, - link_text=link_text, - )|indent(4)>> - ((* elif entry_type == "OneLineEntry" *)) - <<entry["one_line"]( - name=value.name, - 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, - date=value.month_and_year, - doi=value.doi, - doi_url=value.doi_url, - )|indent(4)>> - ((* endif *)) - ((* endfor *)) -((* endmacro *)) diff --git a/rendercv/templates/classic/options.py b/rendercv/templates/classic/options.py new file mode 100644 index 0000000..e69de29 diff --git a/rendercv/templates/fonts/EBGaramond/COPYING b/rendercv/templates/fonts/EBGaramond/COPYING deleted file mode 100644 index 392771d..0000000 --- a/rendercv/templates/fonts/EBGaramond/COPYING +++ /dev/null @@ -1,93 +0,0 @@ -Copyright (c) 2010-2013 Georg Duffner (http://www.georgduffner.at) - -All "EB Garamond" Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/rendercv/templates/fonts/EBGaramond/EBGaramond-Bold.ttf b/rendercv/templates/fonts/EBGaramond/EBGaramond-Bold.ttf deleted file mode 100644 index b73dee0..0000000 Binary files a/rendercv/templates/fonts/EBGaramond/EBGaramond-Bold.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/EBGaramond/EBGaramond-BoldItalic.ttf b/rendercv/templates/fonts/EBGaramond/EBGaramond-BoldItalic.ttf deleted file mode 100644 index 852be7c..0000000 Binary files a/rendercv/templates/fonts/EBGaramond/EBGaramond-BoldItalic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/EBGaramond/EBGaramond-Italic.ttf b/rendercv/templates/fonts/EBGaramond/EBGaramond-Italic.ttf deleted file mode 100644 index 0f76a8e..0000000 Binary files a/rendercv/templates/fonts/EBGaramond/EBGaramond-Italic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/EBGaramond/EBGaramond-Regular.ttf b/rendercv/templates/fonts/EBGaramond/EBGaramond-Regular.ttf deleted file mode 100644 index d3d6f3f..0000000 Binary files a/rendercv/templates/fonts/EBGaramond/EBGaramond-Regular.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/Roboto/LICENSE b/rendercv/templates/fonts/Roboto/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/rendercv/templates/fonts/Roboto/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/rendercv/templates/fonts/Roboto/Roboto-Bold.ttf b/rendercv/templates/fonts/Roboto/Roboto-Bold.ttf deleted file mode 100644 index 43da14d..0000000 Binary files a/rendercv/templates/fonts/Roboto/Roboto-Bold.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/Roboto/Roboto-BoldItalic.ttf b/rendercv/templates/fonts/Roboto/Roboto-BoldItalic.ttf deleted file mode 100644 index bcfdab4..0000000 Binary files a/rendercv/templates/fonts/Roboto/Roboto-BoldItalic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/Roboto/Roboto-Italic.ttf b/rendercv/templates/fonts/Roboto/Roboto-Italic.ttf deleted file mode 100644 index 1b5eaa3..0000000 Binary files a/rendercv/templates/fonts/Roboto/Roboto-Italic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/Roboto/Roboto-Regular.ttf b/rendercv/templates/fonts/Roboto/Roboto-Regular.ttf deleted file mode 100644 index ddf4bfa..0000000 Binary files a/rendercv/templates/fonts/Roboto/Roboto-Regular.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/SourceSans3/LICENSE.md b/rendercv/templates/fonts/SourceSans3/LICENSE.md deleted file mode 100644 index d2b80be..0000000 --- a/rendercv/templates/fonts/SourceSans3/LICENSE.md +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010-2022 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/rendercv/templates/fonts/SourceSans3/SourceSans3-Bold.ttf b/rendercv/templates/fonts/SourceSans3/SourceSans3-Bold.ttf deleted file mode 100644 index 55f6138..0000000 Binary files a/rendercv/templates/fonts/SourceSans3/SourceSans3-Bold.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/SourceSans3/SourceSans3-BoldItalic.ttf b/rendercv/templates/fonts/SourceSans3/SourceSans3-BoldItalic.ttf deleted file mode 100644 index ddeed16..0000000 Binary files a/rendercv/templates/fonts/SourceSans3/SourceSans3-BoldItalic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/SourceSans3/SourceSans3-Italic.ttf b/rendercv/templates/fonts/SourceSans3/SourceSans3-Italic.ttf deleted file mode 100644 index 8ea9acf..0000000 Binary files a/rendercv/templates/fonts/SourceSans3/SourceSans3-Italic.ttf and /dev/null differ diff --git a/rendercv/templates/fonts/SourceSans3/SourceSans3-Regular.ttf b/rendercv/templates/fonts/SourceSans3/SourceSans3-Regular.ttf deleted file mode 100644 index 803d4da..0000000 Binary files a/rendercv/templates/fonts/SourceSans3/SourceSans3-Regular.ttf and /dev/null differ diff --git a/rendercv/templates/new_input.yaml.j2 b/rendercv/templates/new_input.yaml.j2 deleted file mode 100644 index 199c3e1..0000000 --- a/rendercv/templates/new_input.yaml.j2 +++ /dev/null @@ -1,218 +0,0 @@ -cv: - name: <<name>> - label: Mechanical Engineer - location: TX, USA - email: johndoe@example.com - phone: "+33749882538" - website: https://example.com - social_networks: - - network: GitHub - username: johndoe - - network: LinkedIn - username: johndoe - summary: - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porta - vitae dolor vel placerat. Class aptent taciti sociosqu ad litora torquent per conubia - nostra, per inceptos himenaeos. Phasellus ullamcorper, neque id varius dignissim, - tellus sem maximus risus, at lobortis nisl sem id ligula. - section_order: - - Education - - Work Experience - - Academic Projects - - Certificates - - Personal Projects - - Skills - - Test Scores - - Extracurricular Activities - - Publications - - My Custom Section - - My Other Custom Section - - My Third Custom Section - - My Final Custom Section - education: - - institution: My University - url: https://boun.edu.tr - area: Mechanical Engineering - study_type: BS - location: Ankara, Türkiye - start_date: "2017-09-01" - end_date: "2023-01-01" - transcript_url: https://example.com - gpa: 3.99/4.00 - highlights: - - "Class rank: 1 of 62" - - institution: The University of Texas at Austin - url: https://utexas.edu - area: Mechanical Engineering, Student Exchange Program - location: Austin, TX, USA - start_date: "2021-08-01" - end_date: "2022-01-15" - transcript_url: https://example.com - gpa: 4.00/4.00 - work_experience: - - company: CERN - position: Mechanical Engineer - location: Geneva, Switzerland - url: https://home.cern - start_date: "2023-02-01" - end_date: present - highlights: - - CERN is a research organization that operates the world's largest and most - powerful particle accelerator. - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna aliqua. - - Id leo in vitae turpis massa sed, posuere aliquam ultrices sagittis orci a - scelerisque, lorem ipsum dolor sit amet. - - company: AmIACompany - position: Summer Intern - location: Istanbul, Türkiye - url: https://example.com - start_date: "2022-06-15" - end_date: "2022-08-01" - highlights: - - AmIACompany is a technology company that provides web-based engineering - applications that enable the simulation and optimization of products and - manufacturing tools. - - Modeled and simulated a metal-forming process deep drawing using finite element - analysis with open-source software called CalculiX. - academic_projects: - - name: Design and Construction of a Robot - location: Istanbul, Türkiye - date: Fall 2022 - highlights: - - Designed and constructed a controllable robot that measures a car's torque and - power output at different speeds for my senior design project. - url: https://example.com - - name: Design and Construction of an Another Robot - location: Istanbul, Türkiye - date: Fall 2020 - highlights: - - Designed, built, and programmed a microcontroller-based device that plays a - guitar with DC motors as part of a mechatronics course term project. - url: https://example.com - publications: - - title: Phononic band gaps induced by inertial amplification in periodic media - authors: - - Author 1 - - John Doe - - Author 3 - journal: Physical Review B - doi: 10.1103/PhysRevB.76.054309 - date: "2007-08-01" - cited_by: 243 - certificates: - - name: Machine Learning by Stanford University - date: "2022-09-01" - url: https://example.com - skills: - - name: Programming - details: C++, C, Python, JavaScript, MATLAB, Lua, LaTeX - - name: OS - details: Windows, Ubuntu - - name: Other tools - details: Git, Premake, HTML, CSS, React - - name: Languages - details: English (Advanced), French (Lower Intermediate), Turkish (Native) - test_scores: - - name: TOEFL - date: "2022-10-01" - details: - "113/120 — Reading: 29/30, Listening: 30/30, Speaking: 27/30, Writing: - 27/30" - url: https://example.com - - name: GRE - details: "Verbal Reasoning: 160/170, Quantitative Reasoning: 170/170, Analytical - Writing: 5.5/6" - url: https://example.com - personal_projects: - - name: Ray Tracing in C++ - date: Spring 2021 - highlights: - - Coded a ray tracer in C++ that can render scenes with multiple light sources, - spheres, and planes with reflection and refraction properties. - url: https://example.com - extracurricular_activities: - - company: Dumanlikiz Skiing Club - position: Co-founder / Skiing Instructor - location: Chamonix, France - date: Summer 2017 and 2018 - highlights: - - Taught skiing during winters as a certified skiing instructor. - custom_sections: - - title: My Custom Section - entry_type: OneLineEntry - entries: - - name: Testing custom sections - details: Wohooo! - - name: This is a - details: OneLineEntry! - - title: My Other Custom Section - entry_type: EducationEntry - entries: - - institution: Hop! - area: Hop! - study_type: HA - highlights: - - "There are only five types of entries: *EducationEntry*, *ExperienceEntry*, - *NormalEntry*, *OneLineEntry*, and *PublicationEntry*." - - This is an EducationEntry! - start_date: "2022-06-15" - end_date: "2022-08-01" - - title: My Third Custom Section - entry_type: ExperienceEntry - entries: - - company: Hop! - position: Hop! - date: My Date - location: My Location - highlights: - - I think this is really working. This is an *ExperienceEntry*! - - - title: My Final Custom Section - entry_type: NormalEntry - link_text: My Link Text - entries: - - name: This is a normal entry! - url: https://example.com - highlights: - - You don't have to specify a *date* or **location** every time. - - You can use *Markdown* in the **highlights**! - - "Special characters test: üğç" - -design: - theme: classic - font: SourceSans3 - font_size: 10pt - page_size: a4paper - options: - primary_color: rgb(0,79,144) - date_and_location_width: 3.6 cm - show_timespan_in: - - Work Experience - - My Other Custom Section - show_last_updated_date: True - text_alignment: justified - header_font_size: 30 pt - - margins: - page: - top: 2 cm - bottom: 2 cm - left: 1.24 cm - right: 1.24 cm - section_title: - top: 0.2 cm - bottom: 0.2 cm - - entry_area: - left_and_right: 0.2 cm - vertical_between: 0.2 cm - - highlights_area: - top: 0.10 cm - left: 0.4 cm - vertical_between_bullet_points: 0.10 cm - - header: - vertical_between_name_and_connections: 0.2 cm - bottom: 0.2 cm \ No newline at end of file