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
Mayfield 4 years ago committed by GitHub
parent ef6ae61c5e
commit 72c892e014
  1. 4
      Pipfile
  2. 4
      Pipfile.lock
  3. 48
      common/markdown.py
  4. 26
      common/tests/test_markdown.py
  5. 2
      selfdrive/test/test_updated.py
  6. 9
      selfdrive/updated.py

@ -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

4
Pipfile.lock generated

@ -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'&amp;'),
(r'"', r'&quot;'),
]
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()

@ -53,7 +53,7 @@ class TestUpdated(unittest.TestCase):
f"cd {self.basedir} && scons -j{os.cpu_count()} cereal/ common/" f"cd {self.basedir} && scons -j{os.cpu_count()} cereal/ common/"
]) ])
self.params = Params(db=os.path.join(self.basedir, "persist/params")) self.params = Params(os.path.join(self.basedir, "persist/params"))
self.params.clear_all() self.params.clear_all()
os.sync() os.sync()

@ -35,6 +35,7 @@ from pathlib import Path
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.markdown import parse_markdown
from common.params import Params from common.params import Params
from selfdrive.hardware import EON, TICI, HARDWARE from selfdrive.hardware import EON, TICI, HARDWARE
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
@ -113,9 +114,11 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) -
if new_version: if new_version:
try: try:
with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f:
r = f.read() r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes
r = r[:r.find(b'\n\n')] # Slice latest release notes try:
params.put("ReleaseNotes", r + b"\n") params.put("ReleaseNotes", parse_markdown(r.decode("utf-8")))
except Exception:
params.put("ReleaseNotes", r + b"\n")
except Exception: except Exception:
params.put("ReleaseNotes", "") params.put("ReleaseNotes", "")
params.put_bool("UpdateAvailable", True) params.put_bool("UpdateAvailable", True)

Loading…
Cancel
Save