ui: render markdown in release notes (#22754)
* convert release notes from markdown to html
* fall back to previous behavior if utf8 decoding or markdown parsing throws
* make simple markdown parser to avoid needing a library
* add unit test
* move markdown parser to common. add unit test
use `markdown-it-py` instead of `markdown` dependency for test comparison since it's already in Pipfile.lock
* test (almost) all release notes and add some extra html encoding
* update lock
Co-authored-by: Willem Melching <willem.melching@gmail.com>
old-commit-hash: 1aebe6ff6e
commatwo_master
parent
ef6ae61c5e
commit
72c892e014
6 changed files with 85 additions and 8 deletions
@ -1,3 +1,3 @@ |
|||||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||||
oid sha256:75cf958eb51e024e9a7a8a2f5c74d392be9d103bc5d35848d6b4c2e71a0d8580 |
oid sha256:a8c79f0c17747345aaa88d127cd7253ce72cab155af167a89d825f99454353fc |
||||||
size 2026 |
size 2047 |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
version https://git-lfs.github.com/spec/v1 |
version https://git-lfs.github.com/spec/v1 |
||||||
oid sha256:a292a1cd9ece673c2ad294801ff742c38584280844a303a5a0bfa2982f58672c |
oid sha256:0964a435b0151e3ea435d32213877612e3706b7651bd89233e341f792b7a6751 |
||||||
size 264815 |
size 264794 |
||||||
|
@ -0,0 +1,48 @@ |
|||||||
|
from typing import List |
||||||
|
|
||||||
|
HTML_REPLACEMENTS = [ |
||||||
|
(r'&', r'&'), |
||||||
|
(r'"', r'"'), |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
def parse_markdown(text: str, tab_length: int = 2) -> str: |
||||||
|
lines = text.split("\n") |
||||||
|
output: List[str] = [] |
||||||
|
list_level = 0 |
||||||
|
|
||||||
|
def end_outstanding_lists(level: int, end_level: int) -> int: |
||||||
|
while level > end_level: |
||||||
|
level -= 1 |
||||||
|
output.append("</ul>") |
||||||
|
if level > 0: |
||||||
|
output.append("</li>") |
||||||
|
return end_level |
||||||
|
|
||||||
|
for i, line in enumerate(lines): |
||||||
|
if i + 1 < len(lines) and lines[i + 1].startswith("==="): # heading |
||||||
|
output.append(f"<h1>{line}</h1>") |
||||||
|
elif line.startswith("==="): |
||||||
|
pass |
||||||
|
elif line.lstrip().startswith("* "): # list |
||||||
|
line_level = 1 + line.count(" " * tab_length, 0, line.index("*")) |
||||||
|
if list_level >= line_level: |
||||||
|
list_level = end_outstanding_lists(list_level, line_level) |
||||||
|
else: |
||||||
|
list_level += 1 |
||||||
|
if list_level > 1: |
||||||
|
output[-1] = output[-1].replace("</li>", "") |
||||||
|
output.append("<ul>") |
||||||
|
output.append(f"<li>{line.replace('*', '', 1).lstrip()}</li>") |
||||||
|
else: |
||||||
|
list_level = end_outstanding_lists(list_level, 0) |
||||||
|
if len(line) > 0: |
||||||
|
output.append(line) |
||||||
|
|
||||||
|
end_outstanding_lists(list_level, 0) |
||||||
|
output_str = "\n".join(output) + "\n" |
||||||
|
|
||||||
|
for (fr, to) in HTML_REPLACEMENTS: |
||||||
|
output_str = output_str.replace(fr, to) |
||||||
|
|
||||||
|
return output_str |
@ -0,0 +1,26 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
from markdown_it import MarkdownIt |
||||||
|
import os |
||||||
|
import unittest |
||||||
|
|
||||||
|
from common.basedir import BASEDIR |
||||||
|
from common.markdown import parse_markdown |
||||||
|
|
||||||
|
|
||||||
|
class TestMarkdown(unittest.TestCase): |
||||||
|
# validate that our simple markdown parser produces the same output as `markdown_it` from pip |
||||||
|
def test_current_release_notes(self): |
||||||
|
self.maxDiff = None |
||||||
|
|
||||||
|
with open(os.path.join(BASEDIR, "RELEASES.md")) as f: |
||||||
|
for r in f.read().split("\n\n"): |
||||||
|
|
||||||
|
# No hyperlink support is ok |
||||||
|
if '[' in r: |
||||||
|
continue |
||||||
|
|
||||||
|
self.assertEqual(MarkdownIt().render(r), parse_markdown(r)) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
Loading…
Reference in new issue