diff --git a/docs/assets/images/classic.png b/docs/assets/images/classic.png index d16b28ee..e6207921 100644 Binary files a/docs/assets/images/classic.png and b/docs/assets/images/classic.png differ diff --git a/docs/assets/images/sb2nov.png b/docs/assets/images/sb2nov.png index fb3ade64..2441f35e 100644 Binary files a/docs/assets/images/sb2nov.png and b/docs/assets/images/sb2nov.png differ diff --git a/examples/John_Doe_ClassicTheme_CV.pdf b/examples/John_Doe_ClassicTheme_CV.pdf index 1b888aa7..0ce0eae0 100644 Binary files a/examples/John_Doe_ClassicTheme_CV.pdf and b/examples/John_Doe_ClassicTheme_CV.pdf differ diff --git a/examples/John_Doe_ClassicTheme_CV.yaml b/examples/John_Doe_ClassicTheme_CV.yaml index cdd7f32b..1b8f7ede 100644 --- a/examples/John_Doe_ClassicTheme_CV.yaml +++ b/examples/John_Doe_ClassicTheme_CV.yaml @@ -141,3 +141,8 @@ design: bottom: 0.3 cm horizontal_between_connections: 0.5 cm show_timespan_in: [] +rendercv_settings: + output_folder_name: rendercv_output + no_html: false + no_markdown: false + no_png: false diff --git a/examples/John_Doe_EngineeringresumesTheme_CV.pdf b/examples/John_Doe_EngineeringresumesTheme_CV.pdf index 3f2f231e..f71b011e 100644 Binary files a/examples/John_Doe_EngineeringresumesTheme_CV.pdf and b/examples/John_Doe_EngineeringresumesTheme_CV.pdf differ diff --git a/examples/John_Doe_EngineeringresumesTheme_CV.yaml b/examples/John_Doe_EngineeringresumesTheme_CV.yaml index ef7aa852..bbb19ac7 100644 --- a/examples/John_Doe_EngineeringresumesTheme_CV.yaml +++ b/examples/John_Doe_EngineeringresumesTheme_CV.yaml @@ -139,3 +139,8 @@ design: vertical_between_name_and_connections: 5 pt bottom: 5 pt horizontal_between_connections: 10 pt +rendercv_settings: + output_folder_name: rendercv_output + no_html: false + no_markdown: false + no_png: false diff --git a/examples/John_Doe_ModerncvTheme_CV.pdf b/examples/John_Doe_ModerncvTheme_CV.pdf index 8882792a..7815edee 100644 Binary files a/examples/John_Doe_ModerncvTheme_CV.pdf and b/examples/John_Doe_ModerncvTheme_CV.pdf differ diff --git a/examples/John_Doe_ModerncvTheme_CV.yaml b/examples/John_Doe_ModerncvTheme_CV.yaml index 5768d7f3..9467af83 100644 --- a/examples/John_Doe_ModerncvTheme_CV.yaml +++ b/examples/John_Doe_ModerncvTheme_CV.yaml @@ -112,3 +112,8 @@ design: content_scale: 0.75 show_only_years: false disable_page_numbers: false +rendercv_settings: + output_folder_name: rendercv_output + no_html: false + no_markdown: false + no_png: false diff --git a/examples/John_Doe_Sb2novTheme_CV.pdf b/examples/John_Doe_Sb2novTheme_CV.pdf index 4eabd8c1..d8500941 100644 Binary files a/examples/John_Doe_Sb2novTheme_CV.pdf and b/examples/John_Doe_Sb2novTheme_CV.pdf differ diff --git a/examples/John_Doe_Sb2novTheme_CV.yaml b/examples/John_Doe_Sb2novTheme_CV.yaml index 67430060..468be202 100644 --- a/examples/John_Doe_Sb2novTheme_CV.yaml +++ b/examples/John_Doe_Sb2novTheme_CV.yaml @@ -139,3 +139,8 @@ design: vertical_between_name_and_connections: 0.3 cm bottom: 0.3 cm horizontal_between_connections: 0.5 cm +rendercv_settings: + output_folder_name: rendercv_output + no_html: false + no_markdown: false + no_png: false diff --git a/rendercv/cli/commands.py b/rendercv/cli/commands.py index d4bbd132..46506d5e 100644 --- a/rendercv/cli/commands.py +++ b/rendercv/cli/commands.py @@ -5,7 +5,7 @@ commands of RenderCV. import os import pathlib -from typing import Annotated, Literal, Optional +from typing import Annotated, Optional import typer from rich import print @@ -141,18 +141,38 @@ def cli_command_render( input_file_path: pathlib.Path = utilities.string_to_file_path( input_file_name ) # type: ignore - output_directory = pathlib.Path.cwd() / output_folder_name - paths: dict[ - Literal["latex", "pdf", "markdown", "html", "png"], Optional[pathlib.Path] - ] = { - "latex": utilities.string_to_file_path(latex_path), - "pdf": utilities.string_to_file_path(pdf_path), - "markdown": utilities.string_to_file_path(markdown_path), - "html": utilities.string_to_file_path(html_path), - "png": utilities.string_to_file_path(png_path), + # dictionary for command line arguments: + cli_args = { + "use_local_latex_command": use_local_latex_command, + "output_folder_name": output_folder_name, + "latex_path": utilities.string_to_file_path(latex_path), + "pdf_path": utilities.string_to_file_path(pdf_path), + "markdown_path": utilities.string_to_file_path(markdown_path), + "html_path": utilities.string_to_file_path(html_path), + "png_path": utilities.string_to_file_path(png_path), + "no_markdown": dont_generate_markdown, + "no_html": dont_generate_html, + "no_png": dont_generate_png, } + # Create the default values for the cli_args: + cli_args_default = { + "use_local_latex_command": None, + "output_folder_name": "rendercv_output", + "latex_path": None, + "pdf_path": None, + "markdown_path": None, + "html_path": None, + "png_path": None, + "no_markdown": False, + "no_html": False, + "no_png": False, + } + + # keep the current working directory: + working_directory = pathlib.Path.cwd() + # change the current working directory to the input file's directory (because # the template overrides are looked up in the current working directory): os.chdir(input_file_path.parent) @@ -164,17 +184,8 @@ def cli_command_render( # 4. render PNG files from the PDF # 5. generate the Markdown file # 6. render the Markdown file to a HTML (for Grammarly) - number_of_steps = 6 - if dont_generate_png: - number_of_steps = number_of_steps - 1 - if dont_generate_markdown: - # if the Markdown file is not generated, then the HTML file is not generated - number_of_steps = number_of_steps - 2 - else: - if dont_generate_html: - number_of_steps = number_of_steps - 1 - - with printer.LiveProgressReporter(number_of_steps) as progress: + initial_steps = 1 + with printer.LiveProgressReporter(number_of_steps=initial_steps) as progress: progress.start_a_step("Reading and validating the input file") data_as_a_dict = data.read_a_yaml_file(input_file_path) @@ -187,60 +198,96 @@ def cli_command_render( data_as_a_dict = utilities.set_or_update_values( data_as_a_dict, key_and_values ) + # update the data of the rendercv settings: + render_cv_settings = data_as_a_dict.get("rendercv_settings", dict()) + render_cv_settings = utilities.build_rendercv_settings( + render_cv_settings, cli_args, cli_args_default + ) + + # update the data model with the rendercv settings: + data_as_a_dict["rendercv_settings"] = render_cv_settings data_model = data.validate_input_dictionary_and_return_the_data_model( data_as_a_dict ) + output_directory = ( + working_directory / data_model.rendercv_settings.output_folder_name + ) + progress.finish_the_current_step() + # Calculate the number of steps: + number_of_steps = 6 + if data_model.rendercv_settings.no_png: + number_of_steps -= 1 + if data_model.rendercv_settings.no_markdown: + number_of_steps -= 2 + else: + if data_model.rendercv_settings.no_html: + number_of_steps -= 1 + + progress.update_total_steps(number_of_steps) + progress.start_a_step("Generating the LaTeX file") latex_file_path_in_output_folder = ( renderer.create_a_latex_file_and_copy_theme_files( data_model, output_directory ) ) - if paths["latex"]: - utilities.copy_files(latex_file_path_in_output_folder, paths["latex"]) + if data_model.rendercv_settings.latex_path: + utilities.copy_files( + latex_file_path_in_output_folder, + data_model.rendercv_settings.latex_path, + ) progress.finish_the_current_step() progress.start_a_step("Rendering the LaTeX file to a PDF") pdf_file_path_in_output_folder = renderer.render_a_pdf_from_latex( latex_file_path_in_output_folder, use_local_latex_command ) - if paths["pdf"]: - utilities.copy_files(pdf_file_path_in_output_folder, paths["pdf"]) + if data_model.rendercv_settings.pdf_path: + utilities.copy_files( + pdf_file_path_in_output_folder, data_model.rendercv_settings.pdf_path + ) progress.finish_the_current_step() - if not dont_generate_png: + if not data_model.rendercv_settings.no_png: progress.start_a_step("Rendering PNG files from the PDF") png_file_paths_in_output_folder = renderer.render_pngs_from_pdf( pdf_file_path_in_output_folder ) - if paths["png"]: - utilities.copy_files(png_file_paths_in_output_folder, paths["png"]) + if data_model.rendercv_settings.png_path: + utilities.copy_files( + png_file_paths_in_output_folder, + data_model.rendercv_settings.png_path, + ) progress.finish_the_current_step() - if not dont_generate_markdown: + if not data_model.rendercv_settings.no_markdown: progress.start_a_step("Generating the Markdown file") markdown_file_path_in_output_folder = renderer.create_a_markdown_file( data_model, output_directory ) - if paths["markdown"]: + if data_model.rendercv_settings.markdown_path: utilities.copy_files( - markdown_file_path_in_output_folder, paths["markdown"] + markdown_file_path_in_output_folder, + data_model.rendercv_settings.markdown_path, ) progress.finish_the_current_step() - if not dont_generate_html: + if not data_model.rendercv_settings.no_html: progress.start_a_step( "Rendering the Markdown file to a HTML (for Grammarly)" ) html_file_path_in_output_folder = renderer.render_an_html_from_markdown( markdown_file_path_in_output_folder ) - if paths["html"]: - utilities.copy_files(html_file_path_in_output_folder, paths["html"]) + if data_model.rendercv_settings.html_path: + utilities.copy_files( + html_file_path_in_output_folder, + data_model.rendercv_settings.html_path, + ) progress.finish_the_current_step() diff --git a/rendercv/cli/printer.py b/rendercv/cli/printer.py index 525b5df2..efa67984 100644 --- a/rendercv/cli/printer.py +++ b/rendercv/cli/printer.py @@ -84,6 +84,22 @@ class LiveProgressReporter(rich.live.Live): f"{self.current_step_name} has started." ) + def update_total_steps(self, total_steps: int): + """Update the total number of steps. + + Args: + total_steps (int): The total number of steps. + """ + self.number_of_steps = total_steps + self.overall_progress.update( + self.overall_task_id, + total=total_steps, + description=( + f"[bold #AAAAAA]({self.current_step} out of" + f" {self.number_of_steps} steps finished)" + ), + ) + def finish_the_current_step(self): """Finish the current step and update the progress bars.""" self.step_progress.stop_task(self.current_step_id) diff --git a/rendercv/cli/utilities.py b/rendercv/cli/utilities.py index c24cf4eb..38032d23 100644 --- a/rendercv/cli/utilities.py +++ b/rendercv/cli/utilities.py @@ -260,3 +260,35 @@ def parse_render_command_override_arguments( key_and_values[key] = value return key_and_values + + +def build_rendercv_settings( + dictionary: dict, + command_line_arguments: dict[str, str], + command_line_arguments_default_values: dict[str, str], +) -> dict[str, str]: + """Build the RenderCV settings dictionary by combining the dictionary and the command line arguments. + + Args: + dictionary (dict): The dictionary to be combined with the command line + arguments. + command_line_arguments (dict[str, str]): The command line arguments. + + Returns: + dict[str, str]: The combined dictionary. + """ + # Combine the dictionary and the command line arguments if the values are not None: + for key, value in command_line_arguments.items(): + # check if the key is present in the both command line arguments and the default values: + if key in command_line_arguments_default_values: + default_value = command_line_arguments_default_values[key] + if value != default_value: + dictionary = set_or_update_a_value(dictionary, key, str(value)) + else: + # The key is not present in the default values, set the value: + # throw an error reporting this + raise ValueError( + f"The key ({key}) is not present in the default values of the command" + " line arguments!" + ) + return dictionary diff --git a/rendercv/data/models/rendercv_data_model.py b/rendercv/data/models/rendercv_data_model.py index 7be1dda2..324314f0 100644 --- a/rendercv/data/models/rendercv_data_model.py +++ b/rendercv/data/models/rendercv_data_model.py @@ -12,6 +12,7 @@ from .base import RenderCVBaseModelWithoutExtraKeys from .curriculum_vitae import CurriculumVitae from .design import RenderCVDesign from .locale_catalog import LocaleCatalog +from .rendercv_settings import RenderCVSettings class RenderCVDataModel(RenderCVBaseModelWithoutExtraKeys): @@ -36,6 +37,11 @@ class RenderCVDataModel(RenderCVBaseModelWithoutExtraKeys): ), validate_default=True, ) + rendercv_settings: RenderCVSettings = pydantic.Field( + default=RenderCVSettings(), + title="RenderCV Settings", + description="The settings of the RenderCV.", + ) @pydantic.field_validator("locale_catalog") @classmethod @@ -46,3 +52,15 @@ class RenderCVDataModel(RenderCVBaseModelWithoutExtraKeys): LocaleCatalog() return locale_catalog + + @pydantic.field_validator("rendercv_settings") + @classmethod + def initialize_rendercv_settings( + cls, rendercv_settings: RenderCVSettings + ) -> RenderCVSettings: + """Even if the rendercv settings are not provided, initialize them with the default + values.""" + if rendercv_settings is None: + RenderCVSettings() + + return rendercv_settings diff --git a/rendercv/data/models/rendercv_settings.py b/rendercv/data/models/rendercv_settings.py new file mode 100644 index 00000000..2fdfb884 --- /dev/null +++ b/rendercv/data/models/rendercv_settings.py @@ -0,0 +1,134 @@ +""" +The `rendercv.models.rendercv_settings` module contains the data model of the +`rendercv_settings` field of the input file. +""" + +from typing import Optional + +import pydantic + + +class RenderCVSettings(pydantic.BaseModel): + """This class is the data model of the rendercv settings. The values of each field + updates the `rendercv_settings` dictionary. + """ + + model_config = pydantic.ConfigDict( + extra="forbid", + validate_default=True, # To initialize the rendercv settings with the default values + ) + + output_folder_name: Optional[str] = pydantic.Field( + default="rendercv_output", + title="Output Folder Name", + description=( + "The name of the folder where the output files will be saved. The default" + ' value is "rendercv_output".' + ), + ) + + use_local_latex_command: Optional[str] = pydantic.Field( + default=None, + title="Local LaTeX Command", + description=( + "The command to compile the LaTeX file to a PDF file. The default value is" + ' "pdflatex".' + ), + ) + + pdf_path: Optional[str] = pydantic.Field( + default=None, + title="PDF Path", + description=( + "The path of the PDF file. If it is not provided, the PDF file will not be" + " generated. The default value is an empty string." + ), + ) + + latex_path: Optional[str] = pydantic.Field( + default=None, + title="LaTeX Path", + description=( + "The path of the LaTeX file. If it is not provided, the LaTeX file will not" + " be generated. The default value is an empty string." + ), + ) + + html_path: Optional[str] = pydantic.Field( + default=None, + title="HTML Path", + description=( + "The path of the HTML file. If it is not provided, the HTML file will not" + " be generated. The default value is an empty string." + ), + ) + + png_path: Optional[str] = pydantic.Field( + default=None, + title="PNG Path", + description=( + "The path of the PNG file. If it is not provided, the PNG file will not be" + " generated. The default value is an empty string." + ), + ) + + markdown_path: Optional[str] = pydantic.Field( + default=None, + title="Markdown Path", + description=( + "The path of the Markdown file. If it is not provided, the Markdown file" + " will not be generated. The default value is an empty string." + ), + ) + + no_html: Optional[bool] = pydantic.Field( + default=False, + title="Generate HTML Flag", + description=( + "A boolean value to determine whether the HTML file will be generated. The" + " default value is False." + ), + ) + + no_markdown: Optional[bool] = pydantic.Field( + default=False, + title="Generate Markdown Flag", + description=( + "A boolean value to determine whether the Markdown file will be generated." + " The default value is False." + ), + ) + + no_png: Optional[bool] = pydantic.Field( + default=False, + title="Generate PNG Flag", + description=( + "A boolean value to determine whether the PNG file will be generated. The" + " default value is False." + ), + ) + + @pydantic.field_validator( + "output_folder_name", + "pdf_path", + "latex_path", + "html_path", + "png_path", + "no_html", + "no_markdown", + "no_png", + ) + @classmethod + def update_settings( + cls, value: Optional[str], info: pydantic.ValidationInfo + ) -> Optional[str]: + """Update the `rendercv_settings` dictionary with the provided values.""" + if value: + rendercv_settings[info.field_name] = value # type: ignore + + return value + + +# Initialize the rendercv settings with the default values +rendercv_settings: dict[str, str] = {} +RenderCVSettings() # Initialize the rendercv settings with the default values diff --git a/rendercv/renderer/renderer.py b/rendercv/renderer/renderer.py index b54d2684..8ea32acd 100644 --- a/rendercv/renderer/renderer.py +++ b/rendercv/renderer/renderer.py @@ -12,7 +12,6 @@ import sys from typing import Optional import fitz - import markdown from .. import data diff --git a/schema.json b/schema.json index 219cd31e..b547d3c8 100644 --- a/schema.json +++ b/schema.json @@ -1687,6 +1687,114 @@ "title": "PublicationEntry", "type": "object" }, + "RenderCVSettings": { + "additionalProperties": false, + "description": "This class is the data model of the rendercv settings. The values of each field\nupdates the `rendercv_settings` dictionary.", + "properties": { + "output_folder_name": { + "default": "rendercv_output", + "description": "The name of the folder where the output files will be saved. The default value is \"rendercv_output\".", + "title": "Output Folder Name", + "oneOf": [ + { + "type": "string" + } + ] + }, + "use_local_latex_command": { + "default": null, + "description": "The command to compile the LaTeX file to a PDF file. The default value is \"pdflatex\".", + "title": "Local LaTeX Command", + "oneOf": [ + { + "type": "string" + } + ] + }, + "pdf_path": { + "default": null, + "description": "The path of the PDF file. If it is not provided, the PDF file will not be generated. The default value is an empty string.", + "title": "PDF Path", + "oneOf": [ + { + "type": "string" + } + ] + }, + "latex_path": { + "default": null, + "description": "The path of the LaTeX file. If it is not provided, the LaTeX file will not be generated. The default value is an empty string.", + "title": "LaTeX Path", + "oneOf": [ + { + "type": "string" + } + ] + }, + "html_path": { + "default": null, + "description": "The path of the HTML file. If it is not provided, the HTML file will not be generated. The default value is an empty string.", + "title": "HTML Path", + "oneOf": [ + { + "type": "string" + } + ] + }, + "png_path": { + "default": null, + "description": "The path of the PNG file. If it is not provided, the PNG file will not be generated. The default value is an empty string.", + "title": "PNG Path", + "oneOf": [ + { + "type": "string" + } + ] + }, + "markdown_path": { + "default": null, + "description": "The path of the Markdown file. If it is not provided, the Markdown file will not be generated. The default value is an empty string.", + "title": "Markdown Path", + "oneOf": [ + { + "type": "string" + } + ] + }, + "no_html": { + "default": false, + "description": "A boolean value to determine whether the HTML file will be generated. The default value is False.", + "title": "Generate HTML Flag", + "oneOf": [ + { + "type": "boolean" + } + ] + }, + "no_markdown": { + "default": false, + "description": "A boolean value to determine whether the Markdown file will be generated. The default value is False.", + "title": "Generate Markdown Flag", + "oneOf": [ + { + "type": "boolean" + } + ] + }, + "no_png": { + "default": false, + "description": "A boolean value to determine whether the PNG file will be generated. The default value is False.", + "title": "Generate PNG Flag", + "oneOf": [ + { + "type": "boolean" + } + ] + } + }, + "title": "RenderCVSettings", + "type": "object" + }, "Sb2novThemeOptions": { "additionalProperties": false, "description": "This class is the data model of the theme options for the `sb2nov` theme.", @@ -2002,6 +2110,27 @@ "default": null, "description": "The locale catalog of the CV to allow the support of multiple languages.", "title": "Locale Catalog" + }, + "rendercv_settings": { + "allOf": [ + { + "$ref": "#/$defs/RenderCVSettings" + } + ], + "default": { + "output_folder_name": "rendercv_output", + "use_local_latex_command": null, + "pdf_path": null, + "latex_path": null, + "html_path": null, + "png_path": null, + "markdown_path": null, + "no_html": false, + "no_markdown": false, + "no_png": false + }, + "description": "The settings of the RenderCV.", + "title": "RenderCV Settings" } }, "required": [