diff --git a/.github/workflows/publish-a-release.yaml b/.github/workflows/publish-a-release.yaml index d4486933..b2c9caab 100644 --- a/.github/workflows/publish-a-release.yaml +++ b/.github/workflows/publish-a-release.yaml @@ -34,7 +34,7 @@ jobs: - name: Create executable run: | - hatch run create-executable + hatch run exe:create - name: Upload executable uses: softprops/action-gh-release@v2 diff --git a/docs/developer_guide/index.md b/docs/developer_guide/index.md index 25493877..b83ca471 100644 --- a/docs/developer_guide/index.md +++ b/docs/developer_guide/index.md @@ -94,7 +94,7 @@ These commands are defined in the [`pyproject.toml`](https://github.com/rendercv ``` - Create an executable version of RenderCV with [PyInstaller](https://www.pyinstaller.org/) ```bash - hatch run create-executables + hatch run exe:creates ``` - Preview the documentation as you write it ```bash diff --git a/docs/dynamic_content_generation.py b/docs/dynamic_content_generation.py index 6a472d5d..24bbd3a6 100644 --- a/docs/dynamic_content_generation.py +++ b/docs/dynamic_content_generation.py @@ -81,7 +81,8 @@ def define_env(env): for theme_file in themes_path.glob(f"{theme}/*.typ"): theme_templates[theme][theme_file.stem] = theme_file.read_text() - # Update ordering of theme templates + # Update ordering of theme templates, if there are more files, add them to the + # end order = [ "Preamble.j2", "Header.j2", @@ -89,12 +90,16 @@ def define_env(env): "SectionEnding.j2", "TextEntry.j2", "BulletEntry.j2", + "NumberedEntry.j2", + "ReversedNumberedEntry.j2", "OneLineEntry.j2", "EducationEntry.j2", "ExperienceEntry.j2", "NormalEntry.j2", "PublicationEntry.j2", ] + remaining_files = set(theme_templates[theme].keys()) - set(order) + order += list(remaining_files) theme_templates[theme] = {key: theme_templates[theme][key] for key in order} if theme != "markdown": @@ -108,6 +113,13 @@ def define_env(env): env.variables["theme_templates"] = theme_templates + theme_components = {} + for theme_file in themes_path.glob("components/*.typ"): + theme_components[theme_file.stem] = theme_file.read_text() + theme_components = {f"{key}.typ": value for key, value in theme_components.items()} + + env.variables["theme_components"] = theme_components + # Available themes strings (put available themes between ``) themes = [f"`{theme}`" for theme in data.available_themes] env.variables["available_themes"] = ", ".join(themes) diff --git a/docs/reference/themes/components.md b/docs/reference/themes/components.md new file mode 100644 index 00000000..b42771ff --- /dev/null +++ b/docs/reference/themes/components.md @@ -0,0 +1,13 @@ +# `rendercv.themes.components` + +## Jinja Templates + +{% for template_name, template in theme_components.items() %} + +### {{ template_name }} + +```typst +{{ template }} +``` + +{% endfor %} \ No newline at end of file diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index c5267874..7d09d1d2 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -55,6 +55,12 @@ This command will generate a directory called `rendercv_output`, which contains - The CV in Markdown format, `Your_Name_CV.md`. - The CV in HTML format, `Your_Name_CV.html`. You can open this file in a web browser and copy-paste the content to Grammarly for proofreading. +To have RenderCV run automatically whenever the YAML input file is updated, use the `--watch` option. + +```bash +rendercv render --watch "Your_Name_CV.yaml" +``` + !!! info Refer to the [here](cli.md#rendercv-render-command) for the complete list of CLI options available for the `render` command. diff --git a/mkdocs.yaml b/mkdocs.yaml index 75116d05..54589db8 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -98,6 +98,7 @@ nav: - sb2nov: reference/themes/sb2nov.md - moderncv: reference/themes/moderncv.md - engineeringclassic: reference/themes/engineeringclassic.md + - components: reference/themes/components.md - Changelog: - Changelog: changelog/index.md diff --git a/pyproject.toml b/pyproject.toml index 0ccbfeea..4d7680a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,8 +159,6 @@ profile-render-command = "python -m cProfile -o render_command.prof -m rendercv update-schema = "python scripts/update_schema.py" # hatch run update-schema # Update `examples` folder: update-examples = "python scripts/update_examples.py" # hatch run update-examples -# Create executables -create-executable = "python scripts/create_executable.py" # hatch run create-executables [tool.hatch.envs.test] template = "default" @@ -187,6 +185,17 @@ build = "mkdocs build --clean --strict" # hatch run docs:build serve = "mkdocs serve" # hatch run docs:serve # Update entry figures in "Structure of the YAML File" page: update-entry-figures = "python scripts/update_entry_figures.py" # hatch run docs:update-entry-figures + + +[tool.hatch.envs.exe] +installer = "uv" +python = "3.13" +dependencies = [ + "pyinstaller==6.11.1", # to build the executable +] +features = ["full"] +[tool.hatch.envs.exe.scripts] +create = "python scripts/create_executable.py" # hatch run exe:create # ====================================================================================== # Virtual Environments Above =========================================================== # ====================================================================================== diff --git a/rendercv/themes/options.py b/rendercv/themes/options.py index 4640e5c7..0016b4af 100644 --- a/rendercv/themes/options.py +++ b/rendercv/themes/options.py @@ -14,14 +14,22 @@ from ..data.models.base import RenderCVBaseModelWithoutExtraKeys # Custom field types: -def validate_typst_dimension(value: str) -> str: - if not re.fullmatch(r"\d+\.?\d*(cm|in|pt|mm|ex|em)", value): +def validate_typst_dimension(dimension: str) -> str: + """Check if the input string is a valid dimension for the Typst theme. + + Args: + dimension: The input string to be validated. + + Returns: + The input string itself if it is a valid dimension. + """ + if not re.fullmatch(r"\d+\.?\d*(cm|in|pt|mm|ex|em)", dimension): message = ( "The value must be a number followed by a unit (cm, in, pt, mm, ex, em)." " For example, 0.1cm." ) raise ValueError(message) - return value + return dimension TypstDimension = Annotated[ @@ -127,6 +135,8 @@ page_show_last_updated_date_field_info = pydantic.Field( class Page(RenderCVBaseModelWithoutExtraKeys): + """Options related to the page.""" + size: PageSize = page_size_field_info top_margin: TypstDimension = page_top_margin_field_info bottom_margin: TypstDimension = page_bottom_margin_field_info @@ -188,6 +198,8 @@ colors_last_updated_date_and_page_numbering_field_info = pydantic.Field( class Colors(RenderCVBaseModelWithoutExtraKeys): + """Color used throughout the CV.""" + text: pydantic_color.Color = colors_text_field_info name: pydantic_color.Color = colors_name_field_info connections: pydantic_color.Color = colors_connections_field_info @@ -237,6 +249,8 @@ text_date_and_location_column_alignment_field_info = pydantic.Field( class Text(RenderCVBaseModelWithoutExtraKeys): + """Options related to text.""" + font_family: FontFamily = text_font_family_field_info font_size: TypstDimension = text_font_size_field_info leading: TypstDimension = text_leading_field_info @@ -262,6 +276,8 @@ links_use_external_link_icon_field_info = pydantic.Field( class Links(RenderCVBaseModelWithoutExtraKeys): + """Options related to links.""" + underline: bool = links_underline_field_info use_external_link_icon: bool = links_use_external_link_icon_field_info @@ -331,6 +347,8 @@ header_alignment_field_info = pydantic.Field( class Header(RenderCVBaseModelWithoutExtraKeys): + """Options related to headers.""" + name_font_family: FontFamily = header_name_font_family_field_info name_font_size: TypstDimension = header_name_font_size_field_info name_bold: bool = header_name_bold_field_info @@ -393,6 +411,8 @@ section_titles_small_caps_field_info = pydantic.Field( class SectionTitles(RenderCVBaseModelWithoutExtraKeys): + """Options related to section titles.""" + type: SectionTitleType = section_titles_type_field_info font_family: FontFamily = section_titles_font_family_field_info font_size: TypstDimension = section_titles_font_size_field_info @@ -459,6 +479,8 @@ entries_show_time_spans_in_field_info = pydantic.Field( class Entries(RenderCVBaseModelWithoutExtraKeys): + """Options related to entries.""" + date_and_location_width: TypstDimension = entries_date_and_location_width_field_info left_and_right_margin: TypstDimension = entries_left_and_right_margin_field_info horizontal_space_between_columns: TypstDimension = ( @@ -506,6 +528,8 @@ highlights_summary_left_margin_field_info = pydantic.Field( class Highlights(RenderCVBaseModelWithoutExtraKeys): + """Options related to highlights.""" + bullet: BulletPoint = highlights_bullet_field_info top_margin: TypstDimension = highlights_top_margin_field_info left_margin: TypstDimension = highlights_left_margin_field_info @@ -537,6 +561,8 @@ entry_base_with_date_date_and_location_column_template_field_info = pydantic.Fie class EntryBaseWithDate(RenderCVBaseModelWithoutExtraKeys): + """Base options for entries with a date.""" + main_column_second_row_template: str = ( entry_base_with_date_main_column_second_row_template_field_info ) @@ -588,6 +614,8 @@ publication_entry_date_and_location_column_template_field_info = pydantic.Field( class PublicationEntry(RenderCVBaseModelWithoutExtraKeys): + """Options related to publication entries.""" + main_column_first_row_template: str = ( publication_entry_main_column_first_row_template_field_info ) @@ -632,6 +660,8 @@ education_entry_degree_column_width_field_info = pydantic.Field( class EducationEntryBase(RenderCVBaseModelWithoutExtraKeys): + """Base options for education entries.""" + main_column_first_row_template: str = ( education_entry_main_column_first_row_template_field_info ) @@ -642,7 +672,7 @@ class EducationEntryBase(RenderCVBaseModelWithoutExtraKeys): class EducationEntry(EntryBaseWithDate, EducationEntryBase): - pass + """Options related to education entries.""" normal_entry_main_column_first_row_template_field_info = pydantic.Field( @@ -656,13 +686,15 @@ normal_entry_main_column_first_row_template_field_info = pydantic.Field( class NormalEntryBase(RenderCVBaseModelWithoutExtraKeys): + """Base options for normal entries.""" + main_column_first_row_template: str = ( normal_entry_main_column_first_row_template_field_info ) class NormalEntry(EntryBaseWithDate, NormalEntryBase): - pass + """Options related to normal entries.""" experience_entry_main_column_first_row_template_field_info = pydantic.Field( @@ -676,13 +708,15 @@ experience_entry_main_column_first_row_template_field_info = pydantic.Field( class ExperienceEntryBase(RenderCVBaseModelWithoutExtraKeys): + """Base options for experience entries.""" + main_column_first_row_template: str = ( experience_entry_main_column_first_row_template_field_info ) class ExperienceEntry(EntryBaseWithDate, ExperienceEntryBase): - pass + """Options related to experience entries.""" one_line_entry_template_field_info = pydantic.Field( @@ -696,6 +730,8 @@ one_line_entry_template_field_info = pydantic.Field( class OneLineEntry(RenderCVBaseModelWithoutExtraKeys): + """Options related to one-line entries.""" + template: str = one_line_entry_template_field_info @@ -727,6 +763,8 @@ entry_types_publication_entry_field_info = pydantic.Field( class EntryTypes(RenderCVBaseModelWithoutExtraKeys): + """Options related to the templates.""" + one_line_entry: OneLineEntry = entry_types_one_line_entry_field_info education_entry: EducationEntry = entry_types_education_entry_field_info normal_entry: NormalEntry = entry_types_normal_entry_field_info @@ -787,6 +825,8 @@ theme_options_entry_types_field_info = pydantic.Field( class ThemeOptions(RenderCVBaseModelWithoutExtraKeys): + """Full design options.""" + theme: Literal["tobeoverwritten"] = theme_options_theme_field_info page: Page = theme_options_page_field_info colors: Colors = theme_options_colors_field_info diff --git a/tests/test_hatch_scripts.py b/tests/test_hatch_scripts.py index 2d719bea..add25fc1 100644 --- a/tests/test_hatch_scripts.py +++ b/tests/test_hatch_scripts.py @@ -1,4 +1,8 @@ +import contextlib +import os +import pathlib import subprocess +import tempfile import pytest @@ -12,15 +16,36 @@ import pytest "precommit", "update-schema", "update-examples", - "create-executable", "docs:build", "docs:update-entry-figures", ], ) -def test_default_format(script_name): - # Check if hatch is installed: - try: - subprocess.run(["hatch", "--version"], check=True) +def test_scripts(script_name): + # If hatch is not installed, just pass the test + with contextlib.suppress(FileNotFoundError): subprocess.run(["hatch", "run", script_name], check=True) - except FileNotFoundError: - pass + + +def test_executable(): + # If hatch is not installed, just pass the test + with contextlib.suppress(FileNotFoundError): + root = pathlib.Path(__file__).parent.parent + bin_folder = root / "bin" + # remove the bin folder if it exists + if bin_folder.exists(): + for file in bin_folder.iterdir(): + file.unlink() + bin_folder.rmdir() + + subprocess.run(["hatch", "run", "exe:create"], check=True) + + executable_path = next(bin_folder.iterdir()) + assert executable_path.is_file() + + with tempfile.TemporaryDirectory() as temp_dir: + os.chdir(temp_dir) + subprocess.run([str(executable_path), "new", "John"], check=True) + subprocess.run([str(executable_path), "render", "John_CV.yaml"], check=True) + pdf_path = pathlib.Path(temp_dir) / "rendercv_output" / "John_CV.pdf" + + assert pdf_path.exists()