diff --git a/docs/strictdoc_04_release_notes.sdoc b/docs/strictdoc_04_release_notes.sdoc index 25d7a24ef..16e0a5e46 100644 --- a/docs/strictdoc_04_release_notes.sdoc +++ b/docs/strictdoc_04_release_notes.sdoc @@ -9,7 +9,9 @@ This document maintains a record of all changes to StrictDoc since November 2023 TITLE: Unreleased work [FREETEXT] -None. +The requirement-to-source traceability feature was extended to support linking requirements to the RST files. + +One more input scenario was handled for the Create Document workflow. When a project config has ``include_doc_paths`` or ``exclude_doc_paths`` search path filters specified, and an input document path contradicts to the provided filters, a validation message is shown. [/FREETEXT] [/SECTION] diff --git a/strictdoc/core/project_config.py b/strictdoc/core/project_config.py index 8cae457b8..6170a6b1c 100644 --- a/strictdoc/core/project_config.py +++ b/strictdoc/core/project_config.py @@ -72,6 +72,7 @@ def __init__( source_root_path: str, include_source_paths: List[str], exclude_source_paths: List[str], + html2pdf_template: Optional[str], reqif_profile: str, config_last_update: Optional[datetime.datetime], ): @@ -108,6 +109,8 @@ def __init__( self.excel_export_fields: Optional[List[str]] = None + self.html2pdf_template: Optional[str] = html2pdf_template + self.reqif_profile: str = reqif_profile self.autouuid_include_sections: bool = False @@ -132,6 +135,7 @@ def default_config(environment: SDocRuntimeEnvironment): source_root_path=os.getcwd(), include_source_paths=[], exclude_source_paths=[], + html2pdf_template=None, reqif_profile=ReqIFProfile.P01_SDOC, config_last_update=None, ) @@ -286,6 +290,7 @@ def load_from_path_or_get_default( config_dict=config_content, environment=environment, config_last_update=config_last_update, + path_to_config=path_to_config, ) @staticmethod @@ -297,6 +302,7 @@ def load_from_string( config_dict=config_dict, environment=environment, config_last_update=None, + path_to_config=None, ) @staticmethod @@ -305,7 +311,11 @@ def _load_from_dictionary( config_dict: dict, environment: SDocRuntimeEnvironment, config_last_update: Optional[datetime.datetime], + path_to_config: Optional[str], ) -> ProjectConfig: + if path_to_config is not None: + assert os.path.isfile(path_to_config) + project_title = ProjectConfig.DEFAULT_PROJECT_TITLE dir_for_sdoc_assets = ProjectConfig.DEFAULT_DIR_FOR_SDOC_ASSETS project_features = ProjectConfig.DEFAULT_FEATURES @@ -316,6 +326,7 @@ def _load_from_dictionary( source_root_path = os.getcwd() include_source_paths = [] exclude_source_paths = [] + html2pdf_template: Optional[str] = None reqif_profile = ReqIFProfile.P01_SDOC if "project" in config_dict: @@ -410,6 +421,23 @@ def _load_from_dictionary( ) sys.exit(1) + html2pdf_template = project_content.get( + "html2pdf_template", html2pdf_template + ) + if html2pdf_template is not None: + assert not os.path.isabs(html2pdf_template) + if path_to_config is not None: + path_to_config_dir = os.path.dirname(path_to_config) + html2pdf_template = os.path.join( + path_to_config_dir, html2pdf_template + ) + if not os.path.isfile(html2pdf_template): + print( # noqa: T201 + "error: strictdoc.toml: 'html2pdf_template': " + f"invalid path to a template file: {html2pdf_template}'." + ) + sys.exit(1) + if "server" in config_dict: # FIXME: Introduce at least a basic validation for the host/port. server_content = config_dict["server"] @@ -431,6 +459,7 @@ def _load_from_dictionary( source_root_path=source_root_path, include_source_paths=include_source_paths, exclude_source_paths=exclude_source_paths, + html2pdf_template=html2pdf_template, reqif_profile=reqif_profile, config_last_update=config_last_update, ) diff --git a/strictdoc/export/html/generators/document_pdf.py b/strictdoc/export/html/generators/document_pdf.py index c4db0cb6b..5dbb10d4f 100644 --- a/strictdoc/export/html/generators/document_pdf.py +++ b/strictdoc/export/html/generators/document_pdf.py @@ -1,3 +1,7 @@ +from typing import Optional + +from jinja2 import Template + from strictdoc import __version__ from strictdoc.backend.sdoc.models.document import Document from strictdoc.core.document_tree_iterator import DocumentTreeIterator @@ -24,6 +28,11 @@ def export( traceability_index.document_tree ) + custom_html2pdf_template: Optional[Template] = None + if project_config.html2pdf_template is not None: + with open(project_config.html2pdf_template) as f: + custom_html2pdf_template = Template(f.read()) + template = html_templates.jinja_environment().get_template( "screens/document/pdf/index.jinja" ) @@ -42,6 +51,7 @@ def export( strictdoc_version=__version__, document_tree=traceability_index.document_tree, document_tree_iterator=document_tree_iterator, + custom_html2pdf_template=custom_html2pdf_template, ) return output diff --git a/strictdoc/export/html/templates/screens/document/pdf/main.jinja b/strictdoc/export/html/templates/screens/document/pdf/main.jinja index 0f79a3fac..668766ae6 100644 --- a/strictdoc/export/html/templates/screens/document/pdf/main.jinja +++ b/strictdoc/export/html/templates/screens/document/pdf/main.jinja @@ -38,6 +38,10 @@ +{% if custom_html2pdf_template is not defined %} {%- include "screens/document/pdf/template/frontpage.jinja" -%} {%- include "screens/document/pdf/template/header.jinja" -%} {%- include "screens/document/pdf/template/footer.jinja" -%} +{% else %} + {{ custom_html2pdf_template.render() }} +{% endif %} diff --git a/tests/end2end/helpers/components/viewtype_selector.py b/tests/end2end/helpers/components/viewtype_selector.py index 1631fce0a..faf724a04 100644 --- a/tests/end2end/helpers/components/viewtype_selector.py +++ b/tests/end2end/helpers/components/viewtype_selector.py @@ -7,6 +7,7 @@ from tests.end2end.helpers.screens.document.screen_document import ( Screen_Document, ) +from tests.end2end.helpers.screens.pdf.screen_pdf import Screen_PDFDocument from tests.end2end.helpers.screens.standalone_document.screen_standalone_document import ( # noqa: E501 Screen_StandaloneDocument, ) @@ -92,3 +93,9 @@ def do_go_to_standalone_document(self) -> Screen_StandaloneDocument: '//*[@data-viewtype_link="standalone_document"]' ) return Screen_StandaloneDocument(self.test_case) + + def do_go_to_pdf_document(self) -> Screen_PDFDocument: + self.do_click_viewtype_handler() + self.assert_viewtype_menu_opened() + self.test_case.click_xpath('//*[@data-viewtype_link="html2pdf"]') + return Screen_PDFDocument(self.test_case) diff --git a/tests/end2end/helpers/screens/pdf/screen_pdf.py b/tests/end2end/helpers/screens/pdf/screen_pdf.py new file mode 100644 index 000000000..8bfca8d91 --- /dev/null +++ b/tests/end2end/helpers/screens/pdf/screen_pdf.py @@ -0,0 +1,12 @@ +from seleniumbase import BaseCase + +from tests.end2end.helpers.screens.screen import Screen + + +class Screen_PDFDocument(Screen): + def __init__(self, test_case: BaseCase) -> None: + assert isinstance(test_case, BaseCase) + super().__init__(test_case) + + def assert_on_pdf_document(self) -> None: + super().assert_on_screen("html2pdf") diff --git a/tests/end2end/screens/pdf/view_pdf_document_custom_template/__init__.py b/tests/end2end/screens/pdf/view_pdf_document_custom_template/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/end2end/screens/pdf/view_pdf_document_custom_template/document.sdoc b/tests/end2end/screens/pdf/view_pdf_document_custom_template/document.sdoc new file mode 100644 index 000000000..ed0d65c16 --- /dev/null +++ b/tests/end2end/screens/pdf/view_pdf_document_custom_template/document.sdoc @@ -0,0 +1,2 @@ +[DOCUMENT] +TITLE: Empty Document diff --git a/tests/end2end/screens/pdf/view_pdf_document_custom_template/html2pdf_template/index.jinja b/tests/end2end/screens/pdf/view_pdf_document_custom_template/html2pdf_template/index.jinja new file mode 100644 index 000000000..3206e19f0 --- /dev/null +++ b/tests/end2end/screens/pdf/view_pdf_document_custom_template/html2pdf_template/index.jinja @@ -0,0 +1 @@ +CUSTOM TEMPLATE HERE diff --git a/tests/end2end/screens/pdf/view_pdf_document_custom_template/strictdoc.toml b/tests/end2end/screens/pdf/view_pdf_document_custom_template/strictdoc.toml new file mode 100644 index 000000000..e2eae1500 --- /dev/null +++ b/tests/end2end/screens/pdf/view_pdf_document_custom_template/strictdoc.toml @@ -0,0 +1,7 @@ +[project] + +features = [ + "HTML2PDF", +] + +html2pdf_template = "html2pdf_template/index.jinja" diff --git a/tests/end2end/screens/pdf/view_pdf_document_custom_template/test_case.py b/tests/end2end/screens/pdf/view_pdf_document_custom_template/test_case.py new file mode 100644 index 000000000..a6f834e2e --- /dev/null +++ b/tests/end2end/screens/pdf/view_pdf_document_custom_template/test_case.py @@ -0,0 +1,39 @@ +import os + +from tests.end2end.e2e_case import E2ECase +from tests.end2end.helpers.components.viewtype_selector import ViewType_Selector +from tests.end2end.helpers.screens.pdf.screen_pdf import Screen_PDFDocument +from tests.end2end.helpers.screens.project_index.screen_project_index import ( + Screen_ProjectIndex, +) +from tests.end2end.server import SDocTestServer + +path_to_this_test_file_folder = os.path.dirname(os.path.abspath(__file__)) + + +class Test(E2ECase): + def test(self): + with SDocTestServer( + input_path=path_to_this_test_file_folder + ) as test_server: + self.open(test_server.get_host_and_port()) + + screen_project_index = Screen_ProjectIndex(self) + + screen_project_index.assert_on_screen() + screen_project_index.assert_contains_document("Empty Document") + + screen_document = screen_project_index.do_click_on_first_document() + + screen_document.assert_on_screen_document() + screen_document.assert_header_document_title("Empty Document") + screen_document.assert_empty_document() + + viewtype_selector = ViewType_Selector(self) + screen_pdf: Screen_PDFDocument = ( + viewtype_selector.do_go_to_pdf_document() + ) + screen_pdf.assert_on_pdf_document() + screen_pdf.assert_not_empty_view() + + screen_pdf.assert_text("CUSTOM TEMPLATE HERE")