From 45602be463d38d94aa8fd7d6618051411cd35c98 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 14 Jan 2026 06:19:09 +1000 Subject: [PATCH 1/3] Fix 'What's New' page formatting and generation Improves the RST conversion logic in 'fetch_releases.py' to correctly handle Markdown headers, code blocks, images, and HTML details tags. Also updates 'conf.py' to use 'sys.executable' for robust script execution. --- docs/_scripts/fetch_releases.py | 117 +++++++++++++++++++++++++++++--- docs/conf.py | 10 +-- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/docs/_scripts/fetch_releases.py b/docs/_scripts/fetch_releases.py index af964a814..e2ca33c93 100644 --- a/docs/_scripts/fetch_releases.py +++ b/docs/_scripts/fetch_releases.py @@ -2,9 +2,11 @@ Dynamically build what's new page based on github releases """ -import requests, re +import re from pathlib import Path +import requests + GITHUB_REPO = "ultraplot/ultraplot" OUTPUT_RST = Path("whats_new.rst") @@ -14,21 +16,116 @@ def format_release_body(text): """Formats GitHub release notes for better RST readability.""" - lines = text.strip().split("\n") + lines = text.split("\n") formatted = [] + in_code_block = False + indent_string = " " + current_indent = 0 for line in lines: - line = line.strip() + # Preserve original line for code blocks, but strip for processing directives + stripped_line = line.strip() + + # Detect Dropdown Start + #
snippet + match_details = re.search( + r"
\s*(.*?)", stripped_line, re.IGNORECASE + ) + if match_details: + summary = match_details.group(1).strip() + formatted.append( + f"{indent_string * current_indent}.. dropdown:: {summary}\n" + ) + current_indent += 1 + continue + + # Detect Dropdown End + if stripped_line == "
": + if current_indent > 0: + current_indent -= 1 + continue + + # Code Block Start/End + if stripped_line.startswith("```"): + if in_code_block: + in_code_block = False + formatted.append("") + else: + in_code_block = True + lang = stripped_line.strip("`").strip() + formatted.append( + f"{indent_string * current_indent}.. code-block:: {lang if lang else 'text'}\n" + ) + continue + + if in_code_block: + # Code lines need 1 extra indent relative to current context + formatted.append(f"{indent_string * (current_indent + 1)}{line}") + continue + + # Normal lines + if not stripped_line: + formatted.append("") + continue + + # Images + # + match_img = re.search( + r'', stripped_line, re.IGNORECASE + ) + if match_img: + src = match_img.group(1) + formatted.append(f"{indent_string * current_indent}.. image:: {src}\n") + continue + + # Markdown Images + # ![alt](url) + match_md_img = re.search(r"!\[([^\]]*)\]\(([^)]+)\)", stripped_line) + if match_md_img: + alt = match_md_img.group(1) + src = match_md_img.group(2) + formatted.append(f"{indent_string * current_indent}.. image:: {src}") + if alt: + formatted.append(f"{indent_string * current_indent} :alt: {alt}") + formatted.append("") + continue + + # Headers + # Note: Previous implementation used ~ for H2 (##). + # But H1 is =, H2 is -. + # So ## should be H3 (~), ### H4 (^), #### H5 (") + if stripped_line.startswith("## "): + title = stripped_line[3:].strip() + formatted.append( + f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'~' * len(title)}\n" + ) + continue + elif stripped_line.startswith("### "): + title = stripped_line[4:].strip() + formatted.append( + f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'^' * len(title)}\n" + ) + continue + elif stripped_line.startswith("#### "): + title = stripped_line[5:].strip() + formatted.append( + f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'"' * len(title)}\n" + ) + continue + + # Links + stripped_line = re.sub( + r"(?`_", stripped_line + ) + + # Italics using _text_ + stripped_line = re.sub(r"(? Date: Wed, 14 Jan 2026 06:25:45 +1000 Subject: [PATCH 2/3] Switch to m2r2 for Markdown to RST conversion Replaces custom manual parsing with m2r2 library for more robust Markdown to ReStructuredText conversion in 'fetch_releases.py'. Adds 'm2r2' to 'environment.yml' dependencies. --- docs/_scripts/fetch_releases.py | 111 +------------------------------- environment.yml | 1 + 2 files changed, 4 insertions(+), 108 deletions(-) diff --git a/docs/_scripts/fetch_releases.py b/docs/_scripts/fetch_releases.py index e2ca33c93..f07eb678e 100644 --- a/docs/_scripts/fetch_releases.py +++ b/docs/_scripts/fetch_releases.py @@ -6,6 +6,7 @@ from pathlib import Path import requests +from m2r2 import convert GITHUB_REPO = "ultraplot/ultraplot" OUTPUT_RST = Path("whats_new.rst") @@ -16,114 +17,8 @@ def format_release_body(text): """Formats GitHub release notes for better RST readability.""" - lines = text.split("\n") - formatted = [] - in_code_block = False - indent_string = " " - current_indent = 0 - - for line in lines: - # Preserve original line for code blocks, but strip for processing directives - stripped_line = line.strip() - - # Detect Dropdown Start - #
snippet - match_details = re.search( - r"
\s*(.*?)", stripped_line, re.IGNORECASE - ) - if match_details: - summary = match_details.group(1).strip() - formatted.append( - f"{indent_string * current_indent}.. dropdown:: {summary}\n" - ) - current_indent += 1 - continue - - # Detect Dropdown End - if stripped_line == "
": - if current_indent > 0: - current_indent -= 1 - continue - - # Code Block Start/End - if stripped_line.startswith("```"): - if in_code_block: - in_code_block = False - formatted.append("") - else: - in_code_block = True - lang = stripped_line.strip("`").strip() - formatted.append( - f"{indent_string * current_indent}.. code-block:: {lang if lang else 'text'}\n" - ) - continue - - if in_code_block: - # Code lines need 1 extra indent relative to current context - formatted.append(f"{indent_string * (current_indent + 1)}{line}") - continue - - # Normal lines - if not stripped_line: - formatted.append("") - continue - - # Images - # - match_img = re.search( - r'', stripped_line, re.IGNORECASE - ) - if match_img: - src = match_img.group(1) - formatted.append(f"{indent_string * current_indent}.. image:: {src}\n") - continue - - # Markdown Images - # ![alt](url) - match_md_img = re.search(r"!\[([^\]]*)\]\(([^)]+)\)", stripped_line) - if match_md_img: - alt = match_md_img.group(1) - src = match_md_img.group(2) - formatted.append(f"{indent_string * current_indent}.. image:: {src}") - if alt: - formatted.append(f"{indent_string * current_indent} :alt: {alt}") - formatted.append("") - continue - - # Headers - # Note: Previous implementation used ~ for H2 (##). - # But H1 is =, H2 is -. - # So ## should be H3 (~), ### H4 (^), #### H5 (") - if stripped_line.startswith("## "): - title = stripped_line[3:].strip() - formatted.append( - f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'~' * len(title)}\n" - ) - continue - elif stripped_line.startswith("### "): - title = stripped_line[4:].strip() - formatted.append( - f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'^' * len(title)}\n" - ) - continue - elif stripped_line.startswith("#### "): - title = stripped_line[5:].strip() - formatted.append( - f"{indent_string * current_indent}{title}\n{indent_string * current_indent}{'"' * len(title)}\n" - ) - continue - - # Links - stripped_line = re.sub( - r"(?`_", stripped_line - ) - - # Italics using _text_ - stripped_line = re.sub(r"(? Date: Wed, 14 Jan 2026 06:34:04 +1000 Subject: [PATCH 3/3] Add lxml-html-clean dependency Fixes ImportError: lxml.html.clean module is now a separate project. This is required by nbsphinx. --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 83a8d3fba..74375c513 100644 --- a/environment.yml +++ b/environment.yml @@ -31,5 +31,6 @@ dependencies: - pyarrow - cftime - m2r2 + - lxml-html-clean - pip: - git+https://github.com/ultraplot/UltraTheme.git