diff --git a/tools/release_converter.py b/tools/release_converter.py
index c8345bea3..e3913b722 100644
--- a/tools/release_converter.py
+++ b/tools/release_converter.py
@@ -32,13 +32,15 @@ Examples:
python release_converter.py path/to/notes.rst --output-email-html email.html
"""
-import re
import argparse
-import sys
import html
+import re
+import sys
# --- PARSING FUNCTION (parse_rst_release_note) ---
+
+
def parse_rst_release_note(rst_text):
"""Parses the RST release note text to extract key information."""
data = {
@@ -67,10 +69,13 @@ def parse_rst_release_note(rst_text):
for i, line_raw in enumerate(lines):
line_stripped = line_raw.strip()
if i > 0:
- prev_line_stripped = lines[i-1].strip()
- if len(prev_line_stripped) > 3 and all(c == '*' for c in prev_line_stripped):
+ prev_line_stripped = lines[i - 1].strip()
+ # Check if the previous line looks like an RST section underline
+ if len(prev_line_stripped) > 3 and \
+ all(c == '*' for c in prev_line_stripped):
if i > 1:
- header_text_line = lines[i-2].strip().lower()
+ # Header text is two lines above the current line
+ header_text_line = lines[i - 2].strip().lower()
if "new features" in header_text_line:
current_section_list = data['features']
elif "bug fixes" in header_text_line:
@@ -78,42 +83,56 @@ def parse_rst_release_note(rst_text):
elif "housekeeping" in header_text_line:
current_section_list = data['housekeeping']
else:
- pass
+ # Not a section we track, keep previous section active
+ pass
+ # Skip the (usually blank) line after the underline
continue
+ # Parse items only if we are within a known section
if current_section_list is not None and line_stripped.startswith('|'):
line_to_parse = line_stripped
- item_match = re.match(r'\|\s*`Issue\s+#(\d+)\s+<([^>]+)>`_\s*-\s*(.*)', line_to_parse)
+ # Try matching the standard 'Issue #' format
+ item_match = re.match(
+ r'\|\s*`Issue\s+#(\d+)\s+<([^>]+)>`_\s*-\s*(.*)',
+ line_to_parse
+ )
if item_match:
- issue_num, url, description = item_match.groups()
- item_data = {
- 'issue': issue_num.strip(),
- 'url': url.strip(),
- 'description': description.strip().rstrip('.')
- }
- current_section_list.append(item_data)
+ issue_num, url, description = item_match.groups()
+ item_data = {
+ 'issue': issue_num.strip(),
+ 'url': url.strip(),
+ 'description': description.strip().rstrip('.')
+ }
+ current_section_list.append(item_data)
else:
- simple_match = re.match(r'\|\s*(.*)', line_to_parse)
- simple_text = simple_match.group(1).strip().rstrip('.') if simple_match else None
- if simple_text:
- item_data = {
- 'issue': None,
- 'url': None,
- 'description': simple_text
- }
- current_section_list.append(item_data)
+ # Fallback for items starting with '|' but without Issue link
+ simple_match = re.match(r'\|\s*(.*)', line_to_parse)
+ simple_text = (simple_match.group(1).strip().rstrip('.')
+ if simple_match else None)
+ if simple_text:
+ item_data = {
+ 'issue': None,
+ 'url': None,
+ 'description': simple_text
+ }
+ current_section_list.append(item_data)
+ # Combine bug fixes and housekeeping for unified output sections
data['bugs_housekeeping'] = data['bug_fixes'] + data['housekeeping']
+ # Add warnings if sections seem empty after parsing
if not data['features']:
print("Warning: No 'New features' items parsed.", file=sys.stderr)
if not data['bugs_housekeeping']:
- print("Warning: No 'Bug fixes' or 'Housekeeping' items parsed.", file=sys.stderr)
+ print("Warning: No 'Bug fixes' or 'Housekeeping' items parsed.",
+ file=sys.stderr)
return data
# --- Helper for Plurals ---
+
+
def pluralize(count, singular, plural=None):
"""Adds 's' for pluralization if count is not 1."""
if count == 1:
@@ -124,32 +143,61 @@ def pluralize(count, singular, plural=None):
# --- Formatting Functions ---
+
+
def format_email_html(data, skip_issues_set=None):
- """Formats the extracted data into HTML suitable for email (Google Doc style)."""
+ """Formats the extracted data into HTML suitable for email."""
if skip_issues_set is None:
skip_issues_set = set()
if not data.get('version'):
return "
Error: Version not found in parsed data.
"
version = data['version']
- release_url = f"https://www.pgadmin.org/docs/pgadmin4/{version}/release_notes_{version.replace('.', '_')}.html"
+ release_url = (
+ f"https://www.pgadmin.org/docs/pgadmin4/{version}/"
+ f"release_notes_{version.replace('.', '_')}.html"
+ )
download_url = "https://www.pgadmin.org/download/"
website_url = "https://www.pgadmin.org/"
- filtered_features = [item for item in data.get('features', []) if item.get('issue') not in skip_issues_set]
- filtered_bugs = [item for item in data.get('bugs_housekeeping', []) if item.get('issue') not in skip_issues_set]
+ # Filter lists first based on skip_issues_set
+ filtered_features = [
+ item for item in data.get('features', [])
+ if item.get('issue') not in skip_issues_set
+ ]
+ filtered_bugs = [
+ item for item in data.get('bugs_housekeeping', [])
+ if item.get('issue') not in skip_issues_set
+ ]
num_features = len(filtered_features)
num_bugs_housekeeping = len(filtered_bugs)
+ # Build HTML output
output = f"pgAdmin 4 v{version} Released
\n"
- output += f"The pgAdmin Development Team is pleased to announce pgAdmin 4 version {version}.
\n"
- output += (f"This release of pgAdmin 4 includes {pluralize(num_features, 'new feature')} "
- f"and {pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/housekeeping change{'s' if num_bugs_housekeeping != 1 else ''}. ")
- output += f'For more details please see the Release Notes.
\n'
- output += 'pgAdmin is the leading Open Source graphical management tool for PostgreSQL. For more information, please see
\n'
- output += f' {website_url}
\n'
+ output += (
+ f"The pgAdmin Development Team is pleased to announce "
+ f"pgAdmin 4 version {version}.
\n"
+ )
+ output += (
+ f"This release of pgAdmin 4 includes "
+ f"{pluralize(num_features, 'new feature')} and "
+ f"{pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/"
+ f"housekeeping change"
+ f"{'s' if num_bugs_housekeeping != 1 else ''}. "
+ )
+ output += (
+ f'For more details please see the '
+ f'Release Notes.
\n'
+ )
+ output += (
+ 'pgAdmin is the leading Open Source graphical management '
+ 'tool for PostgreSQL. For more information, please see
\n'
+ )
+ # Note: Removed ' ' (HTML tab) as it's non-standard; use CSS if needed
+ output += f'{website_url}
\n'
output += "Notable changes in this release include:
\n"
+ # Add features section only if items remain after filtering
if filtered_features:
output += "Features
\n\n"
for item in filtered_features:
@@ -157,17 +205,26 @@ def format_email_html(data, skip_issues_set=None):
output += f" - {desc}.
\n"
output += "
\n"
+ # Add bugs section only if items remain after filtering
if filtered_bugs:
- output += "Bugs/Housekeeping
\n\n"
+ # Note: Heading was "Bugs/Housekeeping" in pasted code,
+ # changed to "Bug fixes" to match GDoc style mentioned earlier
+ output += "Bug fixes
\n\n"
for item in filtered_bugs:
desc = html.escape(item.get('description', 'N/A').strip())
output += f" - {desc}.
\n"
output += "
\n"
- output += "Builds for Windows and macOS are available now, along with a Python Wheel,
"
- output += "Docker Container, RPM, DEB Package, and source code tarball from:
"
+ output += (
+ "
Builds for Windows and macOS are available now, along with "
+ "a Python Wheel,
"
+ )
+ output += (
+ "Docker Container, RPM, DEB Package, and source code tarball from:
"
+ )
output += f'{download_url}
\n'
- output += "--
Release Manager
pgAdmin Project
\n"
+ # Note: Adjusted sign-off slightly from pasted code to match GDoc examples
+ output += "--
The pgAdmin Team
\n"
return output
@@ -180,72 +237,120 @@ def format_markdown(data, skip_issues_set=None):
return "Error: Version not found in parsed data."
version = data['version']
- release_url = f"https://www.pgadmin.org/docs/pgadmin4/{version}/release_notes_{version.replace('.', '_')}.html"
+ release_url = (
+ f"https://www.pgadmin.org/docs/pgadmin4/{version}/"
+ f"release_notes_{version.replace('.', '_')}.html"
+ )
download_url = "https://www.pgadmin.org/download/"
website_url = "https://www.pgadmin.org/"
- filtered_features = [item for item in data.get('features', []) if item.get('issue') not in skip_issues_set]
- filtered_bugs = [item for item in data.get('bugs_housekeeping', []) if item.get('issue') not in skip_issues_set]
+ filtered_features = [
+ item for item in data.get('features', [])
+ if item.get('issue') not in skip_issues_set
+ ]
+ filtered_bugs = [
+ item for item in data.get('bugs_housekeeping', [])
+ if item.get('issue') not in skip_issues_set
+ ]
num_features = len(filtered_features)
num_bugs_housekeeping = len(filtered_bugs)
- output = f"The pgAdmin Development Team is pleased to announce pgAdmin 4 version {version}. "
- output += (f"This release of pgAdmin 4 includes {pluralize(num_features, 'new feature')} "
- f"and {pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/housekeeping change{'s' if num_bugs_housekeeping != 1 else ''}. ")
- output += f"For more details, please see the [release notes]({release_url}).\n \n"
- output += f"pgAdmin is the leading Open Source graphical management tool for PostgreSQL. For more information, please see [the website]({website_url}).\n\n"
+ output = (
+ f"The pgAdmin Development Team is pleased to announce "
+ f"pgAdmin 4 version {version}. "
+ )
+ output += (
+ f"This release of pgAdmin 4 includes "
+ f"{pluralize(num_features, 'new feature')} and "
+ f"{pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/"
+ f"housekeeping change"
+ f"{'s' if num_bugs_housekeeping != 1 else ''}. "
+ )
+ output += (f"For more details, "
+ f"please see the [release notes]({release_url}).")
+ # Ensure markdown paragraph break
+ output += "\n \n"
+ output += (
+ f"pgAdmin is the leading Open Source graphical management "
+ f"tool for PostgreSQL. For more information, please see "
+ f"[the website]({website_url})."
+ )
+ output += "\n\n"
output += "Notable changes in this release include:\n \n"
if filtered_features:
output += "### Features:\n"
for item in filtered_features:
desc = item.get('description', 'N/A').strip()
- # Always omit the link
- output += f"* {desc}.\n"
+ output += f"* {desc}.\n" # Link logic removed
output += "\n"
if filtered_bugs:
output += "### Bugs/Housekeeping:\n"
for item in filtered_bugs:
desc = item.get('description', 'N/A').strip()
- # Always omit the link
- output += f"* {desc}.\n"
+ output += f"* {desc}.\n" # Link logic removed
output += "\n"
- output += f"Builds for Windows and macOS are available now, along with a Python Wheel, Docker Container, RPM, DEB Package, and source code tarball from the [download area]({download_url})."
+ output += (
+ f"Builds for Windows and macOS are available now, along with "
+ f"a Python Wheel, Docker Container, RPM, DEB Package, and "
+ f"source code tarball from the [download area]({download_url})."
+ )
return output
def format_html(data, skip_issues_set=None):
- """Formats the extracted data into HTML for web news articles (no issue links in lists)."""
+ """Formats extracted data into HTML for web news articles."""
if skip_issues_set is None:
skip_issues_set = set()
- import html
+ # Removed local import html, using global one
if not data.get('version'):
return "Error: Version not found in parsed data.
"
version = data['version']
- release_url = f"/docs/pgadmin4/{version}/release_notes_{version.replace('.', '_')}.html"
+ # Use relative paths for web article links
+ release_url = (
+ f"/docs/pgadmin4/{version}/"
+ f"release_notes_{version.replace('.', '_')}.html"
+ )
download_url = "/download"
- filtered_features = [item for item in data.get('features', []) if item.get('issue') not in skip_issues_set]
- filtered_bugs = [item for item in data.get('bugs_housekeeping', []) if item.get('issue') not in skip_issues_set]
+ filtered_features = [
+ item for item in data.get('features', [])
+ if item.get('issue') not in skip_issues_set
+ ]
+ filtered_bugs = [
+ item for item in data.get('bugs_housekeeping', [])
+ if item.get('issue') not in skip_issues_set
+ ]
num_features = len(filtered_features)
num_bugs_housekeeping = len(filtered_bugs)
- output = f"The pgAdmin Development Team is pleased to announce pgAdmin 4 version {version}. "
- output += (f"This release of pgAdmin 4 includes {pluralize(num_features, 'new feature')} "
- f"and {pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/housekeeping change{'s' if num_bugs_housekeeping != 1 else ''}. ")
- output += f'For more details, please see the release notes.
\n'
+ output = (
+ f"The pgAdmin Development Team is pleased to announce "
+ f"pgAdmin 4 version {version}. "
+ )
+ output += (
+ f"This release of pgAdmin 4 includes "
+ f"{pluralize(num_features, 'new feature')} and "
+ f"{pluralize(num_bugs_housekeeping, 'bug fix', 'bug fixes')}/"
+ f"housekeeping change"
+ f"{'s' if num_bugs_housekeeping != 1 else ''}. "
+ )
+ output += (
+ f'For more details, please see the '
+ f'release notes.
\n'
+ )
output += "Notable changes in this release include:
\n"
if filtered_features:
output += "Features:
\n\n"
for item in filtered_features:
desc = html.escape(item.get('description', 'N/A').strip())
- # Always omit the link, keep bolding
+ # Link logic removed, keep bolding for features in news format
output += f" - {desc}.
\n"
output += "
\n"
@@ -253,7 +358,7 @@ def format_html(data, skip_issues_set=None):
output += "Bugs/Housekeeping:
\n\n"
for item in filtered_bugs:
desc = html.escape(item.get('description', 'N/A').strip())
- # Always omit the link
+ # Link logic removed, no bolding for bugs in news format
output += f" - {desc}.
\n"
output += "
\n"
@@ -266,18 +371,23 @@ def format_html(data, skip_issues_set=None):
if __name__ == "__main__":
# --- Setup Argument Parser ---
parser = argparse.ArgumentParser(
- # ***MODIFIED: Updated description***
- description="Converts pgAdmin RST release notes to Email (HTML), Markdown, and HTML (web) formats.\n"
- "Issue links are omitted from lists. Allows skipping specific issues.",
+ description=(
+ "Converts pgAdmin RST release notes to Email (HTML), Markdown, "
+ "and HTML (web) formats.\nIssue links are omitted from lists. "
+ "Allows skipping specific issues."
+ ),
formatter_class=argparse.RawDescriptionHelpFormatter,
- # ***MODIFIED: Updated examples***
- epilog="Examples:\n"
- " # Default: Don't skip issues\n"
- " python release_converter.py path/to/notes.rst\n\n"
- " # Skip issues 8602 and 8603\n"
- " python release_converter.py path/to/notes.rst --skip-issues 8602 8603\n\n"
- " # Save email output, default skip behavior\n"
- " python release_converter.py path/to/notes.rst --output-email-html email.html"
+ epilog=(
+ "Examples:\n"
+ " # Default: Don't skip issues\n"
+ " python release_converter.py path/to/notes.rst\n\n"
+ " # Skip issues 8602 and 8603\n"
+ " python release_converter.py path/to/notes.rst "
+ "--skip-issues 8602 8603\n\n"
+ " # Save email output, default skip behavior\n"
+ " python release_converter.py path/to/notes.rst "
+ "--output-email-html email.html"
+ )
)
parser.add_argument(
"input_file",
@@ -289,16 +399,16 @@ if __name__ == "__main__":
"--output-email-html",
metavar="",
type=str,
- default=None,
+ default=None, # Use None as default for optional args
help="Optional path to save the Email HTML output to a file."
)
parser.add_argument(
"--skip-issues",
metavar="ISSUE_NUM",
type=str,
- nargs='+',
- default=[],
- help="List of issue numbers (e.g., 8602 8603) to skip from output lists."
+ nargs='+', # Expect 1 or more arguments
+ default=None, # Default to None if not provided
+ help="List of issue numbers (e.g., 8602 8603) to skip from lists."
)
args = parser.parse_args()
@@ -309,7 +419,8 @@ if __name__ == "__main__":
input_rst_content = f.read()
print(f"Successfully read file: {args.input_file}", file=sys.stderr)
except FileNotFoundError:
- print(f"Error: Input file not found at '{args.input_file}'", file=sys.stderr)
+ print(f"Error: Input file not found at '{args.input_file}'",
+ file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error reading file '{args.input_file}': {e}", file=sys.stderr)
@@ -320,43 +431,59 @@ if __name__ == "__main__":
parsed_data = parse_rst_release_note(input_rst_content)
if not parsed_data.get('version'):
- print("\nError: Parsing failed to find version. Cannot proceed.", file=sys.stderr)
- sys.exit(1)
+ print("\nError: Parsing failed to find version. Cannot proceed.",
+ file=sys.stderr)
+ sys.exit(1)
# --- Create skip set ---
- skip_issues_set = set(args.skip_issues)
+ # Handle default=None for skip_issues
+ skip_issues_set = set(args.skip_issues) if args.skip_issues else set()
if skip_issues_set:
- print(f"Attempting to skip issues: {', '.join(sorted(list(skip_issues_set)))}", file=sys.stderr)
+ print(f"Attempting to skip issues: "
+ f"{', '.join(sorted(list(skip_issues_set)))}", file=sys.stderr)
# --- Generate the different formats ---
print("Generating output formats...", file=sys.stderr)
- # ***MODIFIED: Removed include_links from calls***
- email_html_output = format_email_html(parsed_data, skip_issues_set=skip_issues_set)
- markdown_output = format_markdown(parsed_data, skip_issues_set=skip_issues_set)
- news_html_output = format_html(parsed_data, skip_issues_set=skip_issues_set)
+ email_html_output = format_email_html(
+ parsed_data, skip_issues_set=skip_issues_set
+ )
+ markdown_output = format_markdown(
+ parsed_data, skip_issues_set=skip_issues_set
+ )
+ news_html_output = format_html(
+ parsed_data, skip_issues_set=skip_issues_set
+ )
print("Format generation complete.", file=sys.stderr)
# --- Handle Outputs ---
if args.output_email_html:
try:
output_filename = args.output_email_html
+ # Recommend .html extension, but allow user override
if not output_filename.lower().endswith(('.html', '.htm')):
- print(f"Warning: Output file '{output_filename}' does not end with .html or .htm. The content is HTML.", file=sys.stderr)
+ print(f"Warning: Output file '{output_filename}' does not end "
+ f"with .html or .htm. The content is HTML.",
+ file=sys.stderr)
with open(output_filename, "w", encoding="utf-8") as f:
f.write(email_html_output)
- print(f"Email HTML output successfully saved to: {output_filename}", file=sys.stderr)
+ print(f"Email HTML output successfully "
+ f"saved to: {output_filename}",
+ file=sys.stderr)
except Exception as e:
- print(f"Error writing Email HTML output to file '{args.output_email_html}': {e}", file=sys.stderr)
+ print(f"Error writing Email HTML output to file "
+ f"'{args.output_email_html}': {e}", file=sys.stderr)
+ # Print to console as fallback if write fails
print("\n--- Email HTML Output ---")
print(email_html_output)
print("\n---------------------------------\n")
else:
+ # Default: Print email HTML to console
print("\n--- Email HTML Output ---")
print(email_html_output)
print("\n---------------------------------\n")
- # --- Output Other Formats (still to console) ---
+ # --- Output Other Formats (always to console) ---
print("--- Markdown Output ---")
print(markdown_output)
print("\n---------------------------------\n")
@@ -365,4 +492,4 @@ if __name__ == "__main__":
print(news_html_output)
print("\n---------------------------------\n")
- print("Script finished.", file=sys.stderr)
\ No newline at end of file
+ print("Script finished.", file=sys.stderr)