diff --git a/living_documentation_generator/generator.py b/living_documentation_generator/generator.py index 37d9100..93f515a 100644 --- a/living_documentation_generator/generator.py +++ b/living_documentation_generator/generator.py @@ -58,7 +58,18 @@ class LivingDocumentationGenerator: PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) ISSUE_PAGE_TEMPLATE_FILE = os.path.join(PROJECT_ROOT, os.pardir, "templates", "issue_detail_page_template.md") - INDEX_PAGE_TEMPLATE_FILE = os.path.join(PROJECT_ROOT, os.pardir, "templates", "_index_page_template.md") + INDEX_NO_STRUCT_TEMPLATE_FILE = os.path.join( + PROJECT_ROOT, os.pardir, "templates", "_index_no_struct_page_template.md" + ) + INDEX_ROOT_LEVEL_TEMPLATE_FILE = os.path.join( + PROJECT_ROOT, os.pardir, "templates", "_index_root_level_page_template.md" + ) + INDEX_ORG_LEVEL_TEMPLATE_FILE = os.path.join( + PROJECT_ROOT, os.pardir, "templates", "_index_org_level_page_template.md" + ) + INDEX_REPO_LEVEL_TEMPLATE_FILE = os.path.join( + PROJECT_ROOT, os.pardir, "templates", "_index_repo_level_page_template.md" + ) def __init__(self): github_token = ActionInputs.get_github_token() @@ -280,7 +291,10 @@ def _generate_markdown_pages(self, issues: dict[str, ConsolidatedIssue]) -> None @param issues: A dictionary containing all consolidated issues. """ issue_page_detail_template = None - issue_index_page_template = None + index_page_template = None + index_root_level_page = None + index_org_level_template = None + index_repo_level_template = None # Load the template files for generating the Markdown pages try: @@ -290,11 +304,35 @@ def _generate_markdown_pages(self, issues: dict[str, ConsolidatedIssue]) -> None logger.error("Issue page template file was not successfully loaded.", exc_info=True) try: - with open(LivingDocumentationGenerator.INDEX_PAGE_TEMPLATE_FILE, "r", encoding="utf-8") as f: - issue_index_page_template = f.read() + with open(LivingDocumentationGenerator.INDEX_NO_STRUCT_TEMPLATE_FILE, "r", encoding="utf-8") as f: + index_page_template = f.read() except IOError: logger.error("Index page template file was not successfully loaded.", exc_info=True) + try: + with open(LivingDocumentationGenerator.INDEX_ROOT_LEVEL_TEMPLATE_FILE, "r", encoding="utf-8") as f: + index_root_level_page = f.read() + except IOError: + logger.error( + "Structured index page template file for root level was not successfully loaded.", exc_info=True + ) + + try: + with open(LivingDocumentationGenerator.INDEX_ORG_LEVEL_TEMPLATE_FILE, "r", encoding="utf-8") as f: + index_org_level_template = f.read() + except IOError: + logger.error( + "Structured index page template file for organization level was not successfully loaded.", exc_info=True + ) + + try: + with open(LivingDocumentationGenerator.INDEX_REPO_LEVEL_TEMPLATE_FILE, "r", encoding="utf-8") as f: + index_repo_level_template = f.read() + except IOError: + logger.error( + "Structured index page template file for repository level was not successfully loaded.", exc_info=True + ) + # Generate a markdown page for every issue for consolidated_issue in issues.values(): self._generate_md_issue_page(issue_page_detail_template, consolidated_issue) @@ -302,10 +340,14 @@ def _generate_markdown_pages(self, issues: dict[str, ConsolidatedIssue]) -> None # Generate an index page with a summary table about all issues if ActionInputs.get_is_structured_output_enabled(): - self._generate_structured_index_page(issue_index_page_template, issues) + self._generate_structured_index_pages(index_repo_level_template, index_org_level_template, issues) + + output_path = ActionInputs.get_output_directory() + with open(os.path.join(output_path, "_index.md"), "w", encoding="utf-8") as f: + f.write(index_root_level_page) else: issues = list(issues.values()) - self._generate_index_page(issue_index_page_template, issues) + self._generate_index_page(index_page_template, issues) logger.info("Markdown page generation - generated `_index.md`") def _generate_md_issue_page(self, issue_page_template: str, consolidated_issue: ConsolidatedIssue) -> None: @@ -347,13 +389,17 @@ def _generate_md_issue_page(self, issue_page_template: str, consolidated_issue: logger.debug("Generated Markdown page: %s.", page_filename) - def _generate_structured_index_page( - self, issue_index_page_template: str, consolidated_issues: dict[str, ConsolidatedIssue] + def _generate_structured_index_pages( + self, + index_repo_level_template: str, + index_org_level_template: str, + consolidated_issues: dict[str, ConsolidatedIssue], ) -> None: """ - Generates a structured index page with a summary of one repository issues. + Generates a set of index pages due to a structured output feature. - @param issue_index_page_template: The template string for generating the index markdown page. + @param index_repo_level_template: The template string for generating the repository level index markdown page. + @param index_org_level_template: The template string for generating the organization level index markdown page. @param consolidated_issues: A dictionary containing all consolidated issues. @return: None """ @@ -367,8 +413,20 @@ def _generate_structured_index_page( # Generate an index page for each repository for repository_id, issues in issues_by_repository.items(): - self._generate_index_page(issue_index_page_template, issues, repository_id) - logger.info("Markdown page generation - generated `_index.md` for %s.", repository_id) + organization_name, _ = repository_id.split("/") + + self._generate_org_level_index_page(index_org_level_template, organization_name) + logger.debug( + "Generated organization level `_index.md` for %s as a cause of structured output feature.", + organization_name, + ) + + self._generate_index_page(index_repo_level_template, issues, repository_id) + logger.debug( + "Generated repository level `_index.md` for %s as a cause of structured output feature.", repository_id + ) + + logger.info("Markdown page generation - generated `_index.md` pages for %s.", repository_id) def _generate_index_page( self, issue_index_page_template: str, consolidated_issues: list[ConsolidatedIssue], repository_id: str = None @@ -395,9 +453,12 @@ def _generate_index_page( # Prepare issues replacement for the index page replacement = { "date": datetime.now().strftime("%Y-%m-%d"), - "issues": issue_table, + "issue-overview-table": issue_table, } + if ActionInputs.get_is_structured_output_enabled(): + replacement["repository_name"] = repository_id.split("/")[1] + # Replace the issue placeholders in the index template index_page = issue_index_page_template.format(**replacement) @@ -409,6 +470,29 @@ def _generate_index_page( with open(os.path.join(index_directory_path, "_index.md"), "w", encoding="utf-8") as f: f.write(index_page) + @staticmethod + def _generate_org_level_index_page(index_org_level_template: str, organization_name: str) -> None: + """ + Generates an organization level index page and save it. + + @param index_org_level_template: The template string for generating the organization level index markdown page. + @param organization_name: The name of the organization. + @return: None + """ + # Prepare issues replacement for the index page + replacement = { + "date": datetime.now().strftime("%Y-%m-%d"), + "organization_name": organization_name, + } + + # Replace the issue placeholders in the index template + org_level_index_page = index_org_level_template.format(**replacement) + + # Create a sub index page file + output_path = os.path.join(ActionInputs.get_output_directory(), organization_name) + with open(os.path.join(output_path, "_index.md"), "w", encoding="utf-8") as f: + f.write(org_level_index_page) + @staticmethod def _generate_markdown_line(consolidated_issue: ConsolidatedIssue) -> str: """ @@ -422,7 +506,8 @@ def _generate_markdown_line(consolidated_issue: ConsolidatedIssue) -> str: number = consolidated_issue.number title = consolidated_issue.title title = title.replace("|", " _ ") - page_filename = consolidated_issue.generate_page_filename() + issue_link_base = consolidated_issue.title.replace(" ", "-").lower() + issue_mdoc_link = f"features#{issue_link_base}" url = consolidated_issue.html_url state = consolidated_issue.state @@ -438,14 +523,14 @@ def _generate_markdown_line(consolidated_issue: ConsolidatedIssue) -> str: # Generate the Markdown issue line WITH extra project data md_issue_line = ( - f"| {organization_name} | {repository_name} | [#{number} - {title}]({page_filename}) |" - f" {linked_to_project} | {status} |[GitHub link]({url}) |\n" + f"| {organization_name} | {repository_name} | [#{number} - {title}]({issue_mdoc_link}) |" + f" {linked_to_project} | {status} |GitHub link |\n" ) else: # Generate the Markdown issue line WITHOUT project data md_issue_line = ( - f"| {organization_name} | {repository_name} | [#{number} - {title}]({page_filename}) |" - f" {state} |[GitHub link]({url}) |\n" + f"| {organization_name} | {repository_name} | [#{number} - {title}]({issue_mdoc_link}) |" + f" {state} |GitHub link |\n" ) return md_issue_line @@ -464,7 +549,7 @@ def _generate_issue_summary_table(consolidated_issue: ConsolidatedIssue) -> str: # Format issue URL as a Markdown link issue_url = consolidated_issue.html_url - issue_url = f"[GitHub link]({issue_url})" if issue_url else None + issue_url = f"GitHub link " if issue_url else None # Define the header for the issue summary table headers = [ diff --git a/living_documentation_generator/utils/utils.py b/living_documentation_generator/utils/utils.py index dfc680e..bb78cf3 100644 --- a/living_documentation_generator/utils/utils.py +++ b/living_documentation_generator/utils/utils.py @@ -47,7 +47,7 @@ def sanitize_filename(filename: str) -> str: @return: The sanitized filename """ # Remove invalid characters for Windows filenames - sanitized_name = re.sub(r'[<>:"/|?*`]', "", filename) + sanitized_name = re.sub(r'[<>:"/|?*#{}()`]', "", filename) # Reduce consecutive periods sanitized_name = re.sub(r"\.{2,}", ".", sanitized_name) # Reduce consecutive spaces to a single space diff --git a/templates/_index_no_struct_page_template.md b/templates/_index_no_struct_page_template.md new file mode 100644 index 0000000..a0d2a70 --- /dev/null +++ b/templates/_index_no_struct_page_template.md @@ -0,0 +1,21 @@ +--- +title: Features +toolbar_title: Features +description_title: Living Documentation +description: > + This is a comprehensive list and brief overview of all mined features. +date: {date} +weight: 0 +--- + +

Feature Summary page

+ +Our project is designed with a myriad of features to ensure seamless user experience, top-tier functionality, and efficient operations. Here, you'll find a summarized list of all these features, their brief descriptions, and links to their detailed documentation. + +

Feature Overview

+ +
+ +{issue-overview-table} + +
diff --git a/templates/_index_org_level_page_template.md b/templates/_index_org_level_page_template.md new file mode 100644 index 0000000..35280c2 --- /dev/null +++ b/templates/_index_org_level_page_template.md @@ -0,0 +1,7 @@ +--- +title: "{organization_name}" +date: {date} +weight: 0 +--- + +This section displays the living documentation for all repositories within the organization: {organization_name} in a structured output. \ No newline at end of file diff --git a/templates/_index_page_template.md b/templates/_index_page_template.md deleted file mode 100644 index dbecb16..0000000 --- a/templates/_index_page_template.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Issue Summary" -toolbar_title: "Issue Summary" -description_title: "Brief overview of all mined issues." -description: "This is a comprehensive list and brief overview of all issues." -date: "{date}" -weight: 0 ---- - -# Issue Summary page - -Our project is designed with a myriad of issues to ensure seamless user experience, top-tier functionality, and efficient operations. Here, you'll find a summarized list of all these issues, their brief descriptions, and links to their detailed documentation. - -## Issue Overview - -{issues} diff --git a/templates/_index_repo_level_page_template.md b/templates/_index_repo_level_page_template.md new file mode 100644 index 0000000..69e2d83 --- /dev/null +++ b/templates/_index_repo_level_page_template.md @@ -0,0 +1,19 @@ +--- +title: "{repository_name}" +date: {date} +weight: 0 +--- + +This section displays all the information about mined features for the repository: {repository_name}. + +

Feature Summary page

+ +Our project is designed with a myriad of features to ensure seamless user experience, top-tier functionality, and efficient operations. Here, you'll find a summarized list of all these features, their brief descriptions, and links to their detailed documentation. + +

Feature Overview

+ +
+ +{issue-overview-table} + +
\ No newline at end of file diff --git a/templates/_index_root_level_page_template.md b/templates/_index_root_level_page_template.md new file mode 100644 index 0000000..11521bb --- /dev/null +++ b/templates/_index_root_level_page_template.md @@ -0,0 +1,9 @@ +--- +title: Liv Doc +toolbar_title: Features +description_title: Living Documentation +description: > + This is a comprehensive list and brief overview of all mined features. +date: {date} +weight: 0 +--- diff --git a/templates/issue_detail_page_template.md b/templates/issue_detail_page_template.md index 7693d16..62efc5f 100644 --- a/templates/issue_detail_page_template.md +++ b/templates/issue_detail_page_template.md @@ -1,13 +1,11 @@ --- title: "{title}" -date: "{date}" +date: {date} weight: 1 --- -# {page_issue_title} - {issue_summary_table} - -## Issue Content + +

Issue Content

{issue_content} diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 1d9cef0..e7864ed 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -46,7 +46,7 @@ def test_make_issue_key(): @pytest.mark.parametrize( "filename_example, expected_filename", [ - ("in<>va::lid//.fi|le?na*me.txt", "invalid.filename.txt"), # Remove invalid characters for Windows filenames + ("in<>va::l#(){}id//.fi|le?*.txt", "invalid.file.txt"), # Remove invalid characters for Windows filenames ("another..invalid...filename.txt", "another.invalid.filename.txt"), # Reduce consecutive periods ( "filename with spaces.txt",