diff --git a/.gitmodules b/.gitmodules index 4d6ae556..e9775719 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "rendercv/tinytex-release"] - path = rendercv/tinytex-release + path = rendercv/renderer/tinytex-release url = https://github.com/sinaatalay/tinytex-release.git diff --git a/rendercv/reader/__init__.py b/rendercv/reader/__init__.py index 4a3a57ec..f039383a 100644 --- a/rendercv/reader/__init__.py +++ b/rendercv/reader/__init__.py @@ -12,44 +12,29 @@ The validators and data format of RenderCV are written using """ from .models import ( - BulletEntry, CurriculumVitae, + SocialNetwork, + available_theme_options, + BulletEntry, EducationEntry, ExperienceEntry, - LocaleCatalog, NormalEntry, OneLineEntry, PublicationEntry, + LocaleCatalog, RenderCVDataModel, - SocialNetwork, - locale_catalog, - read_input_file, -) - -from .generators import ( - generate_json_schema_file, - generate_json_schema, - create_a_sample_yaml_input_file, - get_a_sample_data_model, -) - -from .field_types import ( - available_social_networks, -) - -from .model_types import ( - available_entry_type_names, - available_themes, - Entry, - SectionInput, -) - - -from .computed_fields import ( format_date, ) -from .utilities import set_or_update_a_value, dictionary_to_yaml +from .reader import ( + create_a_sample_data_model, + create_a_sample_yaml_input_file, + generate_json_schema_file, + generate_json_schema, + set_or_update_a_value, + read_input_file, +) + __all__ = [ "OneLineEntry", @@ -62,15 +47,13 @@ __all__ = [ "CurriculumVitae", "LocaleCatalog", "RenderCVDataModel", - "locale_catalog", + "available_theme_options", + "available_themes", + "create_a_sample_data_model", + "create_a_sample_yaml_input_file", "generate_json_schema_file", "generate_json_schema", - "create_a_sample_yaml_input_file", - "get_a_sample_data_model", - "available_entry_type_names", - "available_themes", - "available_social_networks", - "read_input_file", "set_or_update_a_value", - "dictionary_to_yaml", + "read_input_file", + "format_date", ] diff --git a/rendercv/reader/complete_model.py b/rendercv/reader/complete_model.py deleted file mode 100644 index ebb26859..00000000 --- a/rendercv/reader/complete_model.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -The `rendercv.data_models.models` module contains all the Pydantic data models used in -RenderCV. These data models define the data format and the usage of computed fields and -the validators. -""" - -import functools -from typing import Annotated, Optional -import pathlib - -import annotated_types as at -import pydantic -import pydantic_extra_types.phone_numbers as pydantic_phone_numbers - -from ..themes.classic import ClassicThemeOptions -from . import computed_fields as cf -from . import field_types -from . import utilities as util -from . import entry_validators -from . import entry_types - -# Disable Pydantic warnings: -# warnings.filterwarnings("ignore") - -from . import cv_model -from . import design_model -from . import locale_catalog_model - - -class RenderCVDataModel(entry_types.RenderCVBaseModel): - """This class binds both the CV and the design information together.""" - - cv: cv_model.CurriculumVitae = pydantic.Field( - title="Curriculum Vitae", - description="The data of the CV.", - ) - design: design_model.RenderCVDesign = pydantic.Field( - default=ClassicThemeOptions(theme="classic"), - title="Design", - description=( - "The design information of the CV. The default is the classic theme." - ), - ) - locale_catalog: Optional[locale_catalog_model.LocaleCatalog] = pydantic.Field( - default=None, - title="Locale Catalog", - description=( - "The locale catalog of the CV to allow the support of multiple languages." - ), - ) - - -def read_input_file( - file_path_or_contents: pathlib.Path | str, -) -> RenderCVDataModel: - """Read the input file (YAML or JSON) and return them as an instance of - `RenderCVDataModel`, which is a Pydantic data model of RenderCV's data format. - - Args: - file_path_or_contents (str): The path to the input file or the contents of the - input file as a string. - - Returns: - RenderCVDataModel: The data models with $\\LaTeX$ and Markdown strings. - """ - if isinstance(file_path_or_contents, pathlib.Path): - # Check if the file exists: - if not file_path_or_contents.exists(): - raise FileNotFoundError( - f"The input file [magenta]{file_path_or_contents}[/magenta] doesn't" - " exist!" - ) - - # Check the file extension: - accepted_extensions = [".yaml", ".yml", ".json", ".json5"] - if file_path_or_contents.suffix not in accepted_extensions: - user_friendly_accepted_extensions = [ - f"[green]{ext}[/green]" for ext in accepted_extensions - ] - user_friendly_accepted_extensions = ", ".join( - user_friendly_accepted_extensions - ) - raise ValueError( - "The input file should have one of the following extensions:" - f" {user_friendly_accepted_extensions}. The input file is" - f" [magenta]{file_path_or_contents}[/magenta]." - ) - - file_content = file_path_or_contents.read_text(encoding="utf-8") - else: - file_content = file_path_or_contents - - input_as_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content) # type: ignore - - # Validate the parsed dictionary by creating an instance of RenderCVDataModel: - rendercv_data_model = RenderCVDataModel(**input_as_dictionary) - - return rendercv_data_model diff --git a/rendercv/reader/entry_validators.py b/rendercv/reader/entry_validators.py deleted file mode 100644 index 6fef4c94..00000000 --- a/rendercv/reader/entry_validators.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -The `rendercv.data_models.validators` module contains all the functions used to validate -the data models of RenderCV, in addition to Pydantic inner validation. -""" - -import re -from datetime import date as Date -from typing import Optional - -import pydantic - -from . import utilities as util - -from .entry_types import StartDate, EndDate, ArbitraryDate - - diff --git a/rendercv/reader/full_models.py b/rendercv/reader/full_models.py deleted file mode 100644 index e69de29b..00000000 diff --git a/rendercv/reader/generators.py b/rendercv/reader/generators.py deleted file mode 100644 index a5c61620..00000000 --- a/rendercv/reader/generators.py +++ /dev/null @@ -1,392 +0,0 @@ -""" -The `rendercv.data_models.generators` module contains the functions that are used to -generate a sample YAML input file and the JSON schema of RenderCV based on the data -models defined in `rendercv.data_models.models`. -""" - -import json -import pathlib -from typing import Any, Optional - -import pydantic - -from ..themes.classic import ClassicThemeOptions -from ..themes.sb2nov import Sb2novThemeOptions -from ..themes.moderncv import ModerncvThemeOptions -from ..themes.engineeringresumes import EngineeringresumesThemeOptions -from . import models -from . import utilities as utils -from . import entry_validators as field_val - - -def get_a_sample_data_model( - name: str = "John Doe", theme: str = "classic" -) -> models.RenderCVDataModel: - """Return a sample data model for new users to start with. - - Args: - name (str, optional): The name of the person. Defaults to "John Doe". - Returns: - RenderCVDataModel: A sample data model. - """ - # Check if the theme is valid: - if theme not in models.available_themes: - available_themes_string = ", ".join(models.available_themes) - raise ValueError( - f"The theme should be one of the following: {available_themes_string}!" - f' The provided theme is "{theme}".' - ) - - name = name.encode().decode("unicode-escape") - - sections = { - "welcome_to_RenderCV!": [ - ( - "[RenderCV](https://github.com/sinaatalay/rendercv) is a LaTeX-based" - " CV/resume framework. It allows you to create a high-quality CV or" - " resume as a PDF file from a YAML file, with **Markdown syntax" - " support** and **complete control over the LaTeX code**." - ), - ( - "The boilerplate content is taken from" - " [here](https://github.com/dnl-blkv/mcdowell-cv), where a" - " *clean and tidy CV* pattern is proposed by" - " **[Gayle Laakmann McDowell](https://www.gayle.com/)**." - ), - ], - "quick_guide": [ - models.BulletEntry( - bullet=( - "Each section title is arbitrary, and each section contains a list" - " of entries." - ), - ), - models.BulletEntry( - bullet=( - "There are 7 unique entry types: *models.BulletEntry*, *TextEntry*," - " *models.EducationEntry*, *models.ExperienceEntry*," - " *models.NormalEntry*, *models.PublicationEntry*, and" - " *models.OneLineEntry*." - ), - ), - models.BulletEntry( - bullet=( - "Select a section title, pick an entry type, and start writing your" - " section!" - ) - ), - models.BulletEntry( - bullet=( - "[Here](https://docs.rendercv.com/user_guide/), you can find a" - " comprehensive user guide for RenderCV." - ) - ), - ], - "education": [ - models.EducationEntry( - institution="University of Pennsylvania", - area="Computer Science", - degree="BS", - start_date="2000-09", - end_date="2005-05", - highlights=[ - "GPA: 3.9/4.0 ([Transcript](https://example.com))", - ( - "**Coursework:** Computer Architecture, Artificial" - " Intelligence, Comparison of Learning Algorithms," - " Computational Theory" - ), - ], - ), - ], - "experience": [ - models.ExperienceEntry( - company="Apple", - position="Software Engineer", - start_date="2005-06", - end_date="2007-08", - location="Cupertino, CA", - highlights=[ - ( - "Reduced time to render the user's buddy list by 75% by" - " implementing a prediction algorithm" - ), - ( - "Implemented iChat integration with OS X Spotlight Search by" - " creating a tool to extract metadata from saved chat" - " transcripts and provide metadata to a system-wide search" - " database" - ), - ( - "Redesigned chat file format and implemented backward" - " compatibility for search" - ), - ], - ), - models.ExperienceEntry( - company="Microsoft", - position="Lead Student Ambassador", - start_date="2003-09", - end_date="2005-04", - location="Redmond, WA", - highlights=[ - ( - "Promoted to Lead Student Ambassador in the Fall of 2004," - " supervised 10-15 Student Ambassadors" - ), - ( - "Created and taught a computer science course, CSE 099:" - " Software Design and Development" - ), - ], - ), - models.ExperienceEntry( - company="University of Pennsylvania", - position="Head Teaching Assistant", - start_date="2001-10", - end_date="2003-05", - location="Philadelphia, PA", - highlights=[ - ( - "Implemented a user interface for the VS open file switcher" - " (ctrl-tab) and extended it to tool windows" - ), - ( - "Created a service to provide gradient across VS and VS" - " add-ins, optimized its performance via caching" - ), - "Programmer Productivity Research Center (Summers 2001, 2002)", - ( - "Built an app to compute the similarity of all methods in a" - " code base, reducing the time from $\\mathcal{O}(n^2)$ to" - " $\\mathcal{O}(n \\log n)$" - ), - ( - "Created a test case generation tool that creates random XML" - " docs from XML Schema" - ), - ], - ), - models.ExperienceEntry( - company="Microsoft", - position="Software Engineer, Intern", - start_date="2003-06", - end_date="2003-08", - location="Redmond, WA", - highlights=[ - ( - "Automated the extraction and processing of large datasets from" - " legacy systems using SQL and Perl scripts" - ), - ], - ), - ], - "publications": [ - models.PublicationEntry( - title=( - "Magneto-Thermal Thin Shell Approximation for 3D Finite Element" - " Analysis of No-Insulation Coils" - ), - authors=[ - "Albert Smith", - f"***{name}***", - "Jane Derry", - "Harry Tom", - "Frodo Baggins", - ], - date="2004-01", - doi="10.1109/TASC.2023.3340648", - ) - ], - "projects": [ - models.NormalEntry( - name="Multi-User Drawing Tool", - date="[github.com/name/repo](https://github.com/sinaatalay/rendercv)", - highlights=[ - ( - "Developed an electronic classroom where multiple users can" - ' view and simultaneously draw on a "chalkboard" with each' - " person's edits synchronized" - ), - "Tools Used: C++, MFC", - ], - ), - models.NormalEntry( - name="Synchronized Calendar", - date="[github.com/name/repo](https://github.com/sinaatalay/rendercv)", - highlights=[ - ( - "Developed a desktop calendar with globally shared and" - " synchronized calendars, allowing users to schedule meetings" - " with other users" - ), - "Tools Used: C#, .NET, SQL, XML", - ], - ), - models.NormalEntry( - name="Operating System", - date="2002", - highlights=[ - ( - "Developed a UNIX-style OS with a scheduler, file system, text" - " editor, and calculator" - ), - "Tools Used: C", - ], - ), - ], - "additional_experience_and_awards": [ - models.OneLineEntry( - label="Instructor (2003-2005)", - details="Taught 2 full-credit computer science courses", - ), - models.OneLineEntry( - label="Third Prize, Senior Design Project", - details=( - "Awarded 3rd prize for a synchronized calendar project out of 100" - " entries" - ), - ), - ], - "technologies": [ - models.OneLineEntry( - label="Languages", - details="C++, C, Java, Objective-C, C#, SQL, JavaScript", - ), - models.OneLineEntry( - label="Software", - details=".NET, Microsoft SQL Server, XCode, Interface Builder", - ), - ], - } - cv = models.CurriculumVitae( - name=name, - location="Your Location", - email="youremail@yourdomain.com", - phone="+905419999999", # type: ignore - website="https://yourwebsite.com", # type: ignore - social_networks=[ - models.SocialNetwork(network="LinkedIn", username="yourusername"), - models.SocialNetwork(network="GitHub", username="yourusername"), - ], - sections=sections, # type: ignore - ) - - themes = { - "classic": ClassicThemeOptions, - "moderncv": ModerncvThemeOptions, - "sb2nov": Sb2novThemeOptions, - "engineeringresumes": EngineeringresumesThemeOptions, - } - - design = themes[theme](theme=theme) - - return models.RenderCVDataModel(cv=cv, design=design) - - -def create_a_sample_yaml_input_file( - input_file_path: Optional[pathlib.Path] = None, - name: str = "John Doe", - theme: str = "classic", -) -> str: - """Create a sample YAML input file and return it as a string. If the input file path - is provided, then also save the contents to the file. - - Args: - input_file_path (pathlib.Path, optional): The path to save the input file. - Defaults to None. - name (str, optional): The name of the person. Defaults to "John Doe". - theme (str, optional): The theme of the CV. Defaults to "classic". - Returns: - str: The sample YAML input file as a string. - """ - data_model = get_a_sample_data_model(name=name, theme=theme) - - # Instead of getting the dictionary with data_model.model_dump() directly, we - # convert it to JSON and then to a dictionary. Because the YAML library we are - # using sometimes has problems with the dictionary returned by model_dump(). - - # We exclude "cv.sections" because the data model automatically generates them. - # The user's "cv.sections" input is actually "cv.sections_input" in the data - # model. It is shown as "cv.sections" in the YAML file because an alias is being - # used. If"cv.sections" were not excluded, the automatically generated - # "cv.sections" would overwrite the "cv.sections_input". "cv.sections" are - # automatically generated from "cv.sections_input" to make the templating - # process easier. "cv.sections_input" exists for the convenience of the user. - data_model_as_json = data_model.model_dump_json( - exclude_none=True, by_alias=True, exclude={"cv": {"sections"}} - ) - data_model_as_dictionary = json.loads(data_model_as_json) - - yaml_string = utils.dictionary_to_yaml(data_model_as_dictionary) - - if input_file_path is not None: - input_file_path.write_text(yaml_string, encoding="utf-8") - - return yaml_string - - -def generate_json_schema() -> dict[str, Any]: - """Generate the JSON schema of RenderCV. - - JSON schema is generated for the users to make it easier for them to write the input - file. The JSON Schema of RenderCV is saved in the `docs` directory of the repository - and distributed to the users with the - [JSON Schema Store](https://www.schemastore.org/). - - Returns: - dict: The JSON schema of RenderCV. - """ - - class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema): - def generate(self, schema, mode="validation"): # type: ignore - json_schema = super().generate(schema, mode=mode) - - # Basic information about the schema: - json_schema["title"] = "RenderCV" - json_schema["description"] = "RenderCV data model." - json_schema["$id"] = ( - "https://raw.githubusercontent.com/sinaatalay/rendercv/main/schema.json" - ) - json_schema["$schema"] = "http://json-schema.org/draft-07/schema#" - - # Loop through $defs and remove docstring descriptions and fix optional - # fields - for object_name, value in json_schema["$defs"].items(): - # Don't allow additional properties - value["additionalProperties"] = False - - # 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 = { - "type": "null", - } - for field_name, field in value["properties"].items(): - if "anyOf" in field: - if null_type_dict in field["anyOf"]: - field["anyOf"].remove(null_type_dict) - - field["oneOf"] = field["anyOf"] - del field["anyOf"] - - return json_schema - - schema = models.RenderCVDataModel.model_json_schema( - schema_generator=RenderCVSchemaGenerator - ) - - return schema - - -def generate_json_schema_file(json_schema_path: pathlib.Path): - """Generate the JSON schema of RenderCV and save it to a file. - - Args: - json_schema_path (pathlib.Path): The path to save the JSON schema. - """ - schema = generate_json_schema() - schema_json = json.dumps(schema, indent=2, ensure_ascii=False) - json_schema_path.write_text(schema_json, encoding="utf-8") diff --git a/rendercv/reader/model_types.py b/rendercv/reader/model_types.py deleted file mode 100644 index e69de29b..00000000 diff --git a/rendercv/reader/model_validators.py b/rendercv/reader/model_validators.py deleted file mode 100644 index ed2a271d..00000000 --- a/rendercv/reader/model_validators.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional, Any, Type, Literal - -import pathlib -import pydantic -import importlib -import importlib.util - - -# from .types import ( -# available_entry_types, -# available_theme_options, -# available_themes, -# available_entry_type_names, -# # RenderCVBuiltinDesign, -# ) - -from . import utilities as util -from . import field_types -from .models import RenderCVBaseModel diff --git a/rendercv/reader/models.py b/rendercv/reader/models.py deleted file mode 100644 index e69de29b..00000000 diff --git a/rendercv/reader/models/__init__.py b/rendercv/reader/models/__init__.py new file mode 100644 index 00000000..068d97df --- /dev/null +++ b/rendercv/reader/models/__init__.py @@ -0,0 +1,29 @@ +from .curriculum_vitae import CurriculumVitae, SocialNetwork +from .design import available_theme_options +from .entry_types import ( + BulletEntry, + EducationEntry, + ExperienceEntry, + NormalEntry, + OneLineEntry, + PublicationEntry, +) +from .locale_catalog import LocaleCatalog +from .rendercv_data_model import RenderCVDataModel +from .computers import format_date + + +__all__ = [ + "OneLineEntry", + "BulletEntry", + "EducationEntry", + "ExperienceEntry", + "PublicationEntry", + "NormalEntry", + "SocialNetwork", + "CurriculumVitae", + "LocaleCatalog", + "RenderCVDataModel", + "available_theme_options", + "format_date", +] diff --git a/rendercv/reader/computed_fields.py b/rendercv/reader/models/computers.py similarity index 78% rename from rendercv/reader/computed_fields.py rename to rendercv/reader/models/computers.py index 838523c2..02d83197 100644 --- a/rendercv/reader/computed_fields.py +++ b/rendercv/reader/models/computers.py @@ -7,12 +7,9 @@ etc. from datetime import date as Date from typing import Optional +import re -# from .models import locale_catalog, CurriculumVitae - -from . import utilities as util - -# from . import validators as val +from .locale_catalog import locale_catalog def format_date(date: Date, use_full_name: bool = False) -> str: @@ -91,8 +88,8 @@ def compute_time_span_string( elif isinstance(start_date, int) or isinstance(end_date, int): # Then it means one of the dates is year, so time span cannot be more # specific than years. - start_year = util.get_date_object(start_date).year # type: ignore - end_year = util.get_date_object(end_date).year # type: ignore + start_year = get_date_object(start_date).year # type: ignore + end_year = get_date_object(end_date).year # type: ignore time_span_in_years = end_year - start_year @@ -106,8 +103,8 @@ def compute_time_span_string( else: # Then it means both start_date and end_date are in YYYY-MM-DD or YYYY-MM # format. - end_date = util.get_date_object(end_date) # type: ignore - start_date = util.get_date_object(start_date) # type: ignore + end_date = get_date_object(end_date) # type: ignore + start_date = get_date_object(start_date) # type: ignore # Calculate the number of days between start_date and end_date: timespan_in_days = (end_date - start_date).days # type: ignore @@ -176,7 +173,7 @@ def compute_date_string( date_string = str(date) else: try: - date_object = util.get_date_object(date) + date_object = get_date_object(date) if show_only_years: date_string = str(date_object.year) else: @@ -190,7 +187,7 @@ def compute_date_string( start_date = str(start_date) else: # Then it means start_date is either in YYYY-MM-DD or YYYY-MM format - date_object = util.get_date_object(start_date) + date_object = get_date_object(start_date) if show_only_years: start_date = date_object.year else: @@ -203,7 +200,7 @@ def compute_date_string( end_date = str(end_date) else: # Then it means end_date is either in YYYY-MM-DD or YYYY-MM format - date_object = util.get_date_object(end_date) + date_object = get_date_object(end_date) if show_only_years: end_date = date_object.year else: @@ -296,7 +293,7 @@ def compute_connections(cv) -> list[dict[str, str]]: ) if cv.website is not None: - website_placeholder = util.make_a_url_clean(cv.website) + website_placeholder = make_a_url_clean(cv.website) connections.append( { "latex_icon": "\\faLink", @@ -320,7 +317,7 @@ def compute_connections(cv) -> list[dict[str, str]]: "Google Scholar": "\\faGraduationCap", } for social_network in cv.social_networks: - clean_url = util.make_a_url_clean(social_network.url) + clean_url = make_a_url_clean(social_network.url) connection = { "latex_icon": icon_dictionary[social_network.network], "url": social_network.url, @@ -339,36 +336,83 @@ def compute_connections(cv) -> list[dict[str, str]]: return connections -# def compute_sections( -# sections_input: Optional[dict[str, models.SectionInput]], -# ) -> list[models.SectionBase]: -# """Compute the sections of the CV based on the input sections. +def make_a_url_clean(url: str) -> str: + """Make a URL clean by removing the protocol, www, and trailing slashes. -# The original `sections` input is a dictionary where the keys are the section titles -# and the values are the list of entries in that section. This function converts the -# input sections to a list of `SectionBase` objects. This makes it easier to work with -# the sections in the rest of the code. + Example: + ```python + make_a_url_clean("https://www.example.com/") + ``` + returns + `#!python "example.com"` -# Args: -# sections_input (Optional[dict[str, SectionInput]]): The input sections. -# Returns: -# list[SectionBase]: The computed sections. -# """ -# sections: list[models.SectionBase] = [] + Args: + url (str): The URL to make clean. + Returns: + str: The clean URL. + """ + url = url.replace("https://", "").replace("http://", "").replace("www.", "") + if url.endswith("/"): + url = url[:-1] -# if sections_input is not None: -# for title, section_or_entries in sections_input.items(): -# title = util.dictionary_key_to_proper_section_title(title) + return url -# entry_type_name = val.validate_an_entry_type_and_get_entry_type_name( -# section_or_entries[0] -# ) -# section = models.SectionBase( -# title=title, -# entry_type=entry_type_name, -# entries=section_or_entries, -# ) -# sections.append(section) +def get_date_object(date: str | int) -> Date: + """Parse a date string in YYYY-MM-DD, YYYY-MM, or YYYY format and return a + `datetime.date` object. This function is used throughout the validation process of + the data models. -# return sections + Args: + date (str | int): The date string to parse. + Returns: + Date: The parsed date. + """ + if isinstance(date, int): + date_object = Date.fromisoformat(f"{date}-01-01") + elif re.fullmatch(r"\d{4}-\d{2}-\d{2}", date): + # Then it is in YYYY-MM-DD format + date_object = Date.fromisoformat(date) + elif re.fullmatch(r"\d{4}-\d{2}", date): + # Then it is in YYYY-MM format + date_object = Date.fromisoformat(f"{date}-01") + elif re.fullmatch(r"\d{4}", date): + # Then it is in YYYY format + date_object = Date.fromisoformat(f"{date}-01-01") + elif date == "present": + date_object = Date.today() + else: + raise ValueError( + "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or" + " YYYY format." + ) + + return date_object + + +def dictionary_key_to_proper_section_title(key: str) -> str: + """Convert a dictionary key to a proper section title. + + Example: + ```python + dictionary_key_to_proper_section_title("section_title") + ``` + returns + `#!python "Section Title"` + + Args: + key (str): The key to convert to a proper section title. + Returns: + str: The proper section title. + """ + title = key.replace("_", " ") + words = title.split(" ") + + # loop through the words and if the word doesn't contain any uppercase letters, + # capitalize the first letter of the word. If the word contains uppercase letters, + # don't change the word. + proper_title = " ".join( + word.capitalize() if word.islower() else word for word in words + ) + + return proper_title diff --git a/rendercv/reader/cv_model.py b/rendercv/reader/models/curriculum_vitae.py similarity index 99% rename from rendercv/reader/cv_model.py rename to rendercv/reader/models/curriculum_vitae.py index fbf1bbac..4c62520d 100644 --- a/rendercv/reader/cv_model.py +++ b/rendercv/reader/models/curriculum_vitae.py @@ -4,7 +4,7 @@ import pydantic import functools from . import entry_types -from . import computed_fields as cf +from . import computers as cf from typing import Type, Literal import re diff --git a/rendercv/reader/design_model.py b/rendercv/reader/models/design.py similarity index 91% rename from rendercv/reader/design_model.py rename to rendercv/reader/models/design.py index 0853322b..b632732d 100644 --- a/rendercv/reader/design_model.py +++ b/rendercv/reader/models/design.py @@ -1,32 +1,20 @@ -from typing import Annotated, Any +from typing import Annotated, Any, Type import pydantic -from ..themes.classic import ClassicThemeOptions -from ..themes.engineeringresumes import EngineeringresumesThemeOptions -from ..themes.moderncv import ModerncvThemeOptions -from ..themes.sb2nov import Sb2novThemeOptions +from ...themes.classic import ClassicThemeOptions +from ...themes.engineeringresumes import EngineeringresumesThemeOptions +from ...themes.moderncv import ModerncvThemeOptions +from ...themes.sb2nov import Sb2novThemeOptions -from typing import Optional, Any, Type, Literal import pathlib -import pydantic import importlib import importlib.util - -# from .types import ( -# available_entry_types, -# available_theme_options, -# available_themes, -# available_entry_type_names, -# # RenderCVBuiltinDesign, -# ) - -from . import utilities as util -from . import field_types from . import entry_types + # ====================================================================================== # Create validator functions: ========================================================== # ====================================================================================== @@ -139,7 +127,7 @@ def validate_design_options( else: # Then it means there is no __init__.py file in the custom theme folder. # Create a dummy data model and use that instead. - class ThemeOptionsAreNotProvided(RenderCVBaseModel): + class ThemeOptionsAreNotProvided(entry_types.RenderCVBaseModel): theme: str = theme_name theme_data_model = ThemeOptionsAreNotProvided(theme=theme_name) @@ -185,5 +173,3 @@ available_theme_options = { "sb2nov": Sb2novThemeOptions, "engineeringresumes": EngineeringresumesThemeOptions, } - -available_themes = list(available_theme_options.keys()) diff --git a/rendercv/reader/entry_types.py b/rendercv/reader/models/entry_types.py similarity index 99% rename from rendercv/reader/entry_types.py rename to rendercv/reader/models/entry_types.py index 7e49e14d..28b2c8f6 100644 --- a/rendercv/reader/entry_types.py +++ b/rendercv/reader/models/entry_types.py @@ -3,8 +3,8 @@ import pydantic import functools import re from datetime import date as Date -from . import utilities as util -from . import computed_fields as cf +from .. import utilities as util +from . import computers as cf # ====================================================================================== diff --git a/rendercv/reader/locale_catalog_model.py b/rendercv/reader/models/locale_catalog.py similarity index 100% rename from rendercv/reader/locale_catalog_model.py rename to rendercv/reader/models/locale_catalog.py diff --git a/rendercv/reader/models/rendercv_data_model.py b/rendercv/reader/models/rendercv_data_model.py new file mode 100644 index 00000000..ff9e0e1c --- /dev/null +++ b/rendercv/reader/models/rendercv_data_model.py @@ -0,0 +1,42 @@ +""" +The `rendercv.data_models.models` module contains all the Pydantic data models used in +RenderCV. These data models define the data format and the usage of computed fields and +the validators. +""" + +from typing import Optional + +import pydantic + +from . import entry_types +from ...themes.classic import ClassicThemeOptions + +from .design import RenderCVDesign +from .curriculum_vitae import CurriculumVitae +from .locale_catalog import LocaleCatalog + +# Disable Pydantic warnings: +# warnings.filterwarnings("ignore") + + +class RenderCVDataModel(entry_types.RenderCVBaseModel): + """This class binds both the CV and the design information together.""" + + cv: CurriculumVitae = pydantic.Field( + title="Curriculum Vitae", + description="The data of the CV.", + ) + design: RenderCVDesign = pydantic.Field( + default=ClassicThemeOptions(theme="classic"), + title="Design", + description=( + "The design information of the CV. The default is the classic theme." + ), + ) + locale_catalog: Optional[LocaleCatalog] = pydantic.Field( + default=None, + title="Locale Catalog", + description=( + "The locale catalog of the CV to allow the support of multiple languages." + ), + ) diff --git a/rendercv/reader/reader.py b/rendercv/reader/reader.py new file mode 100644 index 00000000..af214dd3 --- /dev/null +++ b/rendercv/reader/reader.py @@ -0,0 +1,309 @@ +""" +The `rendercv.data_models.generators` module contains the functions that are used to +generate a sample YAML input file and the JSON schema of RenderCV based on the data +models defined in `rendercv.data_models.models`. +""" + +import json +import pathlib +from typing import Any, Optional +import io +import pydantic + +import ruamel.yaml + +from . import models + + +def dictionary_to_yaml(dictionary: dict[str, Any]): + """Converts a dictionary to a YAML string. + + Args: + dictionary (dict[str, Any]): The dictionary to be converted to YAML. + Returns: + str: The YAML string. + """ + yaml_object = ruamel.yaml.YAML() + yaml_object.encoding = "utf-8" + yaml_object.width = 60 + yaml_object.indent(mapping=2, sequence=4, offset=2) + with io.StringIO() as string_stream: + yaml_object.dump(dictionary, string_stream) + yaml_string = string_stream.getvalue() + return yaml_string + + +def read_a_yaml_file(file_path_or_contents: pathlib.Path) -> dict[str, Any]: + """Read a YAML file and return its content as a dictionary. The YAML file can be + given as a path to the file or as the contents of the file as a string. + + Args: + file_path_or_contents (pathlib.Path): The path to the YAML file or the contents + of the YAML file as a string. + Returns: + dict: The content of the YAML file as a dictionary. + """ + if isinstance(file_path_or_contents, pathlib.Path): + # Check if the file exists: + if not file_path_or_contents.exists(): + raise FileNotFoundError( + f"The input file [magenta]{file_path_or_contents}[/magenta] doesn't" + " exist!" + ) + + # Check the file extension: + accepted_extensions = [".yaml", ".yml", ".json", ".json5"] + if file_path_or_contents.suffix not in accepted_extensions: + user_friendly_accepted_extensions = [ + f"[green]{ext}[/green]" for ext in accepted_extensions + ] + user_friendly_accepted_extensions = ", ".join( + user_friendly_accepted_extensions + ) + raise ValueError( + "The input file should have one of the following extensions:" + f" {user_friendly_accepted_extensions}. The input file is" + f" [magenta]{file_path_or_contents}[/magenta]." + ) + + file_content = file_path_or_contents.read_text(encoding="utf-8") + else: + file_content = file_path_or_contents + + yaml_as_a_dictionary: dict[str, Any] = ruamel.yaml.YAML().load(file_content) + + return yaml_as_a_dictionary + + +def create_a_sample_data_model( + name: str = "John Doe", theme: str = "classic" +) -> models.RenderCVDataModel: + """Return a sample data model for new users to start with. + + Args: + name (str, optional): The name of the person. Defaults to "John Doe". + Returns: + RenderCVDataModel: A sample data model. + """ + # Check if the theme is valid: + if theme not in models.available_theme_options: + available_themes_string = ", ".join(models.available_theme_options.keys()) + raise ValueError( + f"The theme should be one of the following: {available_themes_string}!" + f' The provided theme is "{theme}".' + ) + + # read the sample_content.yaml file + sample_content = pathlib.Path(__file__).parent / "sample_content.yaml" + sample_content_dictionary = read_a_yaml_file(sample_content) + cv = models.CurriculumVitae(**sample_content_dictionary) + + # Update the name: + name = name.encode().decode("unicode-escape") + cv.name = name + + design = models.available_theme_options[theme](theme=theme) + + return models.RenderCVDataModel(cv=cv, design=design) + + +def create_a_sample_yaml_input_file( + input_file_path: Optional[pathlib.Path] = None, + name: str = "John Doe", + theme: str = "classic", +) -> str: + """Create a sample YAML input file and return it as a string. If the input file path + is provided, then also save the contents to the file. + + Args: + input_file_path (pathlib.Path, optional): The path to save the input file. + Defaults to None. + name (str, optional): The name of the person. Defaults to "John Doe". + theme (str, optional): The theme of the CV. Defaults to "classic". + Returns: + str: The sample YAML input file as a string. + """ + data_model = create_a_sample_data_model(name=name, theme=theme) + + # Instead of getting the dictionary with data_model.model_dump() directly, we + # convert it to JSON and then to a dictionary. Because the YAML library we are + # using sometimes has problems with the dictionary returned by model_dump(). + + # We exclude "cv.sections" because the data model automatically generates them. + # The user's "cv.sections" input is actually "cv.sections_input" in the data + # model. It is shown as "cv.sections" in the YAML file because an alias is being + # used. If"cv.sections" were not excluded, the automatically generated + # "cv.sections" would overwrite the "cv.sections_input". "cv.sections" are + # automatically generated from "cv.sections_input" to make the templating + # process easier. "cv.sections_input" exists for the convenience of the user. + data_model_as_json = data_model.model_dump_json( + exclude_none=True, by_alias=True, exclude={"cv": {"sections"}} + ) + data_model_as_dictionary = json.loads(data_model_as_json) + + yaml_string = dictionary_to_yaml(data_model_as_dictionary) + + if input_file_path is not None: + input_file_path.write_text(yaml_string, encoding="utf-8") + + return yaml_string + + +def generate_json_schema() -> dict[str, Any]: + """Generate the JSON schema of RenderCV. + + JSON schema is generated for the users to make it easier for them to write the input + file. The JSON Schema of RenderCV is saved in the `docs` directory of the repository + and distributed to the users with the + [JSON Schema Store](https://www.schemastore.org/). + + Returns: + dict: The JSON schema of RenderCV. + """ + + class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema): + def generate(self, schema, mode="validation"): # type: ignore + json_schema = super().generate(schema, mode=mode) + + # Basic information about the schema: + json_schema["title"] = "RenderCV" + json_schema["description"] = "RenderCV data model." + json_schema["$id"] = ( + "https://raw.githubusercontent.com/sinaatalay/rendercv/main/schema.json" + ) + json_schema["$schema"] = "http://json-schema.org/draft-07/schema#" + + # Loop through $defs and remove docstring descriptions and fix optional + # fields + for object_name, value in json_schema["$defs"].items(): + # Don't allow additional properties + value["additionalProperties"] = False + + # 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 = { + "type": "null", + } + for field_name, field in value["properties"].items(): + if "anyOf" in field: + if null_type_dict in field["anyOf"]: + field["anyOf"].remove(null_type_dict) + + field["oneOf"] = field["anyOf"] + del field["anyOf"] + + return json_schema + + schema = models.RenderCVDataModel.model_json_schema( + schema_generator=RenderCVSchemaGenerator + ) + + return schema + + +def generate_json_schema_file(json_schema_path: pathlib.Path): + """Generate the JSON schema of RenderCV and save it to a file. + + Args: + json_schema_path (pathlib.Path): The path to save the JSON schema. + """ + schema = generate_json_schema() + schema_json = json.dumps(schema, indent=2, ensure_ascii=False) + json_schema_path.write_text(schema_json, encoding="utf-8") + + +def set_or_update_a_value( + data_model: pydantic.BaseModel | dict | list, + key: str, + value: str, + sub_model: pydantic.BaseModel | dict | list = None, +): + """Set or update a value in a data model for a specific key. For example, a key can + be `cv.sections.education.3.institution` and the value can be "Bogazici University". + + Args: + data_model (pydantic.BaseModel | dict | list): The data model to set or update + the value. + key (str): The key to set or update the value. + value (Any): The value to set or update. + sub_model (pydantic.BaseModel | dict | list, optional): The sub model to set or + update the value. This is used for recursive calls. When the value is set + to a sub model, the original data model is validated. Defaults to None. + """ + # Recursively call this function until the last key is reached: + + # Rename `sections` with `sections_input` since the key is `sections` is an alias: + key = key.replace("sections.", "sections_input.") + keys = key.split(".") + + if sub_model is not None: + model = sub_model + else: + model = data_model + + if len(keys) == 1: + # Set the value: + if value.startswith("{") and value.endswith("}"): + # Allow users to assign dictionaries: + value = eval(value) + elif value.startswith("[") and value.endswith("]"): + # Allow users to assign lists: + value = eval(value) + + if isinstance(model, pydantic.BaseModel): + setattr(model, key, value) + elif isinstance(model, dict): + model[key] = value + elif isinstance(model, list): + model[int(key)] = value + else: + raise ValueError( + "The data model should be either a Pydantic data model, dictionary, or" + " list.", + ) + + data_model = type(data_model).model_validate( + (data_model.model_dump(by_alias=True)) + ) + return data_model + else: + # get the first key and call the function with remaining keys: + first_key = keys[0] + key = ".".join(keys[1:]) + if isinstance(model, pydantic.BaseModel): + sub_model = getattr(model, first_key) + elif isinstance(model, dict): + sub_model = model[first_key] + elif isinstance(model, list): + sub_model = model[int(first_key)] + else: + raise ValueError( + "The data model should be either a Pydantic data model, dictionary, or" + " list.", + ) + + set_or_update_a_value(data_model, key, value, sub_model) + + +def read_input_file( + file_path_or_contents: pathlib.Path | str, +) -> models.RenderCVDataModel: + """Read the input file (YAML or JSON) and return them as an instance of + `RenderCVDataModel`, which is a Pydantic data model of RenderCV's data format. + + Args: + file_path_or_contents (str): The path to the input file or the contents of the + input file as a string. + + Returns: + RenderCVDataModel: The data models with $\\LaTeX$ and Markdown strings. + """ + input_as_dictionary = read_a_yaml_file(file_path_or_contents) + + # Validate the parsed dictionary by creating an instance of RenderCVDataModel: + rendercv_data_model = models.RenderCVDataModel(**input_as_dictionary) + + return rendercv_data_model diff --git a/rendercv/reader/sample_content.yaml b/rendercv/reader/sample_content.yaml new file mode 100644 index 00000000..3effa74b --- /dev/null +++ b/rendercv/reader/sample_content.yaml @@ -0,0 +1,135 @@ +name: John Doe +location: Your Location +email: youremail@yourdomain.com +phone: tel:+90-541-999-99-99 +website: https://yourwebsite.com/ +social_networks: + - network: LinkedIn + username: yourusername + - network: GitHub + username: yourusername +sections: + welcome_to_RenderCV!: + - '[RenderCV](https://github.com/sinaatalay/rendercv) is + a LaTeX-based CV/resume framework. It allows you to create + a high-quality CV or resume as a PDF file from a YAML + file, with **full Markdown syntax support** and **complete + control over the LaTeX code**.' + - The boilerplate content is taken from [here](https://github.com/dnl-blkv/mcdowell-cv), + where a *clean and tidy CV* pattern is proposed by **[Gayle + Laakmann McDowell](https://www.gayle.com/)**. + quick_guide: + - bullet: Each section title is arbitrary, and each section + contains a list of entries. + - bullet: 'There are 7 unique entry types: *BulletEntry*, + *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, + *PublicationEntry*, and *OneLineEntry*.' + - bullet: Select a section title, pick an entry type, and + start writing your section! + - bullet: '[Here](https://docs.rendercv.com/user_guide/), + you can find a comprehensive user guide for RenderCV.' + education: + - institution: University of Pennsylvania + area: Computer Science + degree: BS + start_date: 2000-09 + end_date: 2005-05 + highlights: + - 'GPA: 3.9/4.0 ([Transcript](https://example.com))' + - '**Coursework:** Computer Architecture, Artificial + Intelligence, Comparison of Learning Algorithms, Computational + Theory' + experience: + - company: Apple + position: Software Engineer + location: Cupertino, CA + start_date: 2005-06 + end_date: 2007-08 + highlights: + - Reduced time to render the user's buddy list by 75% + by implementing a prediction algorithm + - Implemented iChat integration with OS X Spotlight + Search by creating a tool to extract metadata from + saved chat transcripts and provide metadata to a system-wide + search database + - Redesigned chat file format and implemented backward + compatibility for search + - company: Microsoft + position: Lead Student Ambassador + location: Redmond, WA + start_date: 2003-09 + end_date: 2005-04 + highlights: + - Promoted to Lead Student Ambassador in the Fall of + 2004, supervised 10-15 Student Ambassadors + - 'Created and taught a computer science course, CSE + 099: Software Design and Development' + - company: University of Pennsylvania + position: Head Teaching Assistant + location: Philadelphia, PA + start_date: 2001-10 + end_date: 2003-05 + highlights: + - Implemented a user interface for the VS open file + switcher (ctrl-tab) and extended it to tool windows + - Created a service to provide gradient across VS and + VS add-ins, optimized its performance via caching + - Programmer Productivity Research Center (Summers 2001, + 2002) + - Built an app to compute the similarity of all methods + in a code base, reducing the time from $\mathcal{O}(n^2)$ + to $\mathcal{O}(n \log n)$ + - Created a test case generation tool that creates random + XML docs from XML Schema + - company: Microsoft + position: Software Engineer, Intern + location: Redmond, WA + start_date: 2003-06 + end_date: 2003-08 + highlights: + - Automated the extraction and processing of large datasets + from legacy systems using SQL and Perl scripts + publications: + - title: Magneto-Thermal Thin Shell Approximation for 3D + Finite Element Analysis of No-Insulation Coils + authors: + - Albert Smith + - '***John Doe***' + - Jane Derry + - Harry Tom + - Frodo Baggins + doi: 10.1109/TASC.2023.3340648 + date: 2004-01 + projects: + - name: Multi-User Drawing Tool + date: '[github.com/name/repo](https://github.com/sinaatalay/rendercv)' + highlights: + - Developed an electronic classroom where multiple users + can view and simultaneously draw on a "chalkboard" + with each person's edits synchronized + - 'Tools Used: C++, MFC' + - name: Synchronized Calendar + date: '[github.com/name/repo](https://github.com/sinaatalay/rendercv)' + highlights: + - Developed a desktop calendar with globally shared + and synchronized calendars, allowing users to schedule + meetings with other users + - 'Tools Used: C#, .NET, SQL, XML' + - name: Operating System + date: 2002 + highlights: + - Developed a UNIX-style OS with a scheduler, file system, + text editor, and calculator + - 'Tools Used: C' + additional_experience_and_awards: + - label: Instructor (2003-2005) + details: Taught 2 full-credit computer science courses + - label: Third Prize, Senior Design Project + details: Awarded 3rd prize for a synchronized calendar + project out of 100 entries + technologies: + - label: Languages + details: C++, C, Java, Objective-C, C#, SQL, JavaScript + - label: Software + details: .NET, Microsoft SQL Server, XCode, Interface + Builder diff --git a/rendercv/reader/utilities.py b/rendercv/reader/utilities.py deleted file mode 100644 index 9732b195..00000000 --- a/rendercv/reader/utilities.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -The `rendercv.data_models.utilities` module contains utility functions that are required -by data models. -""" - -import io -import re -from datetime import date as Date -from typing import Any - -import pydantic -import ruamel.yaml - - -def dictionary_to_yaml(dictionary: dict[str, Any]): - """Converts a dictionary to a YAML string. - - Args: - dictionary (dict[str, Any]): The dictionary to be converted to YAML. - Returns: - str: The YAML string. - """ - yaml_object = ruamel.yaml.YAML() - yaml_object.encoding = "utf-8" - yaml_object.width = 60 - yaml_object.indent(mapping=2, sequence=4, offset=2) - with io.StringIO() as string_stream: - yaml_object.dump(dictionary, string_stream) - yaml_string = string_stream.getvalue() - return yaml_string - - -def get_date_object(date: str | int) -> Date: - """Parse a date string in YYYY-MM-DD, YYYY-MM, or YYYY format and return a - `datetime.date` object. This function is used throughout the validation process of - the data models. - - Args: - date (str | int): The date string to parse. - Returns: - Date: The parsed date. - """ - if isinstance(date, int): - date_object = Date.fromisoformat(f"{date}-01-01") - elif re.fullmatch(r"\d{4}-\d{2}-\d{2}", date): - # Then it is in YYYY-MM-DD format - date_object = Date.fromisoformat(date) - elif re.fullmatch(r"\d{4}-\d{2}", date): - # Then it is in YYYY-MM format - date_object = Date.fromisoformat(f"{date}-01") - elif re.fullmatch(r"\d{4}", date): - # Then it is in YYYY format - date_object = Date.fromisoformat(f"{date}-01-01") - elif date == "present": - date_object = Date.today() - else: - raise ValueError( - "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or" - " YYYY format." - ) - - return date_object - - -def dictionary_key_to_proper_section_title(key: str) -> str: - """Convert a dictionary key to a proper section title. - - Example: - ```python - dictionary_key_to_proper_section_title("section_title") - ``` - returns - `#!python "Section Title"` - - Args: - key (str): The key to convert to a proper section title. - Returns: - str: The proper section title. - """ - title = key.replace("_", " ") - words = title.split(" ") - - # loop through the words and if the word doesn't contain any uppercase letters, - # capitalize the first letter of the word. If the word contains uppercase letters, - # don't change the word. - proper_title = " ".join( - word.capitalize() if word.islower() else word for word in words - ) - - return proper_title - - -def set_or_update_a_value( - data_model: pydantic.BaseModel | dict | list, - key: str, - value: str, - sub_model: pydantic.BaseModel | dict | list = None, -): - """Set or update a value in a data model for a specific key. For example, a key can - be `cv.sections.education.3.institution` and the value can be "Bogazici University". - - Args: - data_model (pydantic.BaseModel | dict | list): The data model to set or update - the value. - key (str): The key to set or update the value. - value (Any): The value to set or update. - sub_model (pydantic.BaseModel | dict | list, optional): The sub model to set or - update the value. This is used for recursive calls. When the value is set - to a sub model, the original data model is validated. Defaults to None. - """ - # Recursively call this function until the last key is reached: - - # Rename `sections` with `sections_input` since the key is `sections` is an alias: - key = key.replace("sections.", "sections_input.") - keys = key.split(".") - - if sub_model is not None: - model = sub_model - else: - model = data_model - - if len(keys) == 1: - # Set the value: - if value.startswith("{") and value.endswith("}"): - # Allow users to assign dictionaries: - value = eval(value) - elif value.startswith("[") and value.endswith("]"): - # Allow users to assign lists: - value = eval(value) - - if isinstance(model, pydantic.BaseModel): - setattr(model, key, value) - elif isinstance(model, dict): - model[key] = value - elif isinstance(model, list): - model[int(key)] = value - else: - raise ValueError( - "The data model should be either a Pydantic data model, dictionary, or" - " list.", - ) - - data_model = type(data_model).model_validate( - (data_model.model_dump(by_alias=True)) - ) - return data_model - else: - # get the first key and call the function with remaining keys: - first_key = keys[0] - key = ".".join(keys[1:]) - if isinstance(model, pydantic.BaseModel): - sub_model = getattr(model, first_key) - elif isinstance(model, dict): - sub_model = model[first_key] - elif isinstance(model, list): - sub_model = model[int(first_key)] - else: - raise ValueError( - "The data model should be either a Pydantic data model, dictionary, or" - " list.", - ) - - set_or_update_a_value(data_model, key, value, sub_model) - - -def make_a_url_clean(url: str) -> str: - """Make a URL clean by removing the protocol, www, and trailing slashes. - - Example: - ```python - make_a_url_clean("https://www.example.com/") - ``` - returns - `#!python "example.com"` - - Args: - url (str): The URL to make clean. - Returns: - str: The clean URL. - """ - url = url.replace("https://", "").replace("http://", "").replace("www.", "") - if url.endswith("/"): - url = url[:-1] - - return url diff --git a/rendercv/tinytex-release b/rendercv/renderer/tinytex-release similarity index 100% rename from rendercv/tinytex-release rename to rendercv/renderer/tinytex-release diff --git a/tests/conftest.py b/tests/conftest.py index 7b9f3ec5..2458c0a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -135,7 +135,7 @@ def text_entry() -> str: @pytest.fixture def rendercv_data_model() -> dm.RenderCVDataModel: """Return a sample RenderCV data model.""" - return dm.get_a_sample_data_model() + return dm.create_a_sample_data_model() @pytest.fixture diff --git a/tests/test_data_models.py b/tests/test_data_models.py index 22ad732e..69351ff3 100644 --- a/tests/test_data_models.py +++ b/tests/test_data_models.py @@ -181,13 +181,13 @@ def test_read_input_file_that_doesnt_exist(tmp_path): dm.available_themes, ) def test_get_a_sample_data_model(theme): - data_model = dm.get_a_sample_data_model("John Doe", theme) + data_model = dm.create_a_sample_data_model("John Doe", theme) assert isinstance(data_model, dm.RenderCVDataModel) def test_get_a_sample_data_model_invalid_theme(): with pytest.raises(ValueError): - dm.get_a_sample_data_model("John Doe", "invalid") + dm.create_a_sample_data_model("John Doe", "invalid") def test_generate_json_schema(): @@ -677,7 +677,7 @@ def test_custom_theme_with_broken_init_file(tmp_path, testdata_directory_path): def test_locale_catalog(): - data_model = dm.get_a_sample_data_model("John Doe") + data_model = dm.create_a_sample_data_model("John Doe") data_model.locale_catalog = dm.LocaleCatalog( month="a", months="b", @@ -719,7 +719,7 @@ def test_locale_catalog(): def test_if_local_catalog_resets(): - data_model = dm.get_a_sample_data_model("John Doe") + data_model = dm.create_a_sample_data_model("John Doe") data_model.locale_catalog = dm.LocaleCatalog( month="a", @@ -727,7 +727,7 @@ def test_if_local_catalog_resets(): assert dm.locale_catalog["month"] == "a" - data_model = dm.get_a_sample_data_model("John Doe") + data_model = dm.create_a_sample_data_model("John Doe") assert dm.locale_catalog["month"] == "month"