bin/gen_release_notes.py: escape special rST characters

Signed-off-by: Eric Engestrom <eric@engestrom.ch>
Reviewed-by: Dylan Baker <dylan@pnwbakers.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/6869>
diff --git a/bin/gen_release_notes.py b/bin/gen_release_notes.py
index 716f807..8801611 100755
--- a/bin/gen_release_notes.py
+++ b/bin/gen_release_notes.py
@@ -25,6 +25,7 @@
 import datetime
 import os
 import pathlib
+import re
 import subprocess
 import sys
 import textwrap
@@ -74,7 +75,7 @@
     ------------
 
     %for f in features:
-    - ${f}
+    - ${rst_escape(f)}
     %endfor
 
 
@@ -82,7 +83,7 @@
     ---------
 
     %for b in bugs:
-    - ${b}
+    - ${rst_escape(b)}
     %endfor
 
 
@@ -91,15 +92,27 @@
     %for c, author_line in changes:
       %if author_line:
 
-    ${c}
+    ${rst_escape(c)}
 
       %else:
-    - ${c}
+    - ${rst_escape(c)}
       %endif
     %endfor
     """))
 
 
+def rst_escape(unsafe_str: str) -> str:
+    "Escape rST special chars when they follow or preceed a whitespace"
+    special = re.escape(r'`<>*_#[]|')
+    unsafe_str = re.sub(r'(^|\s)([' + special + r'])',
+                        r'\1\\\2',
+                        unsafe_str)
+    unsafe_str = re.sub(r'([' + special + r'])(\s|$)',
+                        r'\\\1\2',
+                        unsafe_str)
+    return unsafe_str
+
+
 async def gather_commits(version: str) -> str:
     p = await asyncio.create_subprocess_exec(
         'git', 'log', '--oneline', f'mesa-{version}..', '--grep', r'Closes: \(https\|#\).*',
@@ -249,6 +262,7 @@
                 header_underline=header_underline,
                 previous_version=previous_version,
                 vk_version=CURRENT_VK_VERSION,
+                rst_escape=rst_escape,
             ))
         except:
             print(exceptions.text_error_template().render())