diff --git a/common/markdown.py b/common/markdown.py
new file mode 100644
index 0000000000..f0f056d963
--- /dev/null
+++ b/common/markdown.py
@@ -0,0 +1,45 @@
+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("")
+ if level > 0:
+ output.append("")
+ return end_level
+
+ for i, line in enumerate(lines):
+ if i + 1 < len(lines) and lines[i + 1].startswith("==="): # heading
+ output.append(f"
{line}
")
+ 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("", "")
+ output.append("")
+ output.append(f"- {line.replace('*', '', 1).lstrip()}
")
+ 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
diff --git a/common/tests/test_markdown.py b/common/tests/test_markdown.py
new file mode 100644
index 0000000000..d3c7e02c69
--- /dev/null
+++ b/common/tests/test_markdown.py
@@ -0,0 +1,15 @@
+import os
+
+from openpilot.common.basedir import BASEDIR
+from openpilot.common.markdown import parse_markdown
+
+
+class TestMarkdown:
+ def test_all_release_notes(self):
+ with open(os.path.join(BASEDIR, "RELEASES.md")) as f:
+ release_notes = f.read().split("\n\n")
+ assert len(release_notes) > 10
+
+ for rn in release_notes:
+ md = parse_markdown(rn)
+ assert len(md) > 0
diff --git a/poetry.lock b/poetry.lock
index 23f58ec691..93bf1ae6a0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:84ac396fe236ffaf09f9a865fa3bae19ac961db726bdc19b79e52c2f45db6abe
+oid sha256:c803c02c1a5b9bb67b70a847c862464f49481e0edf44226ec1aeb3f107c5d790
size 618064
diff --git a/pyproject.toml b/pyproject.toml
index 8642eff4cd..34f1e97ab2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -121,7 +121,6 @@ casadi = "*"
future-fstrings = "*"
# these should be removed
-markdown-it-py = "*"
timezonefinder = "*"
setproctitle = "*"
diff --git a/system/updated/updated.py b/system/updated/updated.py
index c1af41bd92..d43f439af5 100755
--- a/system/updated/updated.py
+++ b/system/updated/updated.py
@@ -11,11 +11,11 @@ import time
import threading
from collections import defaultdict
from pathlib import Path
-from markdown_it import MarkdownIt
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.time import system_time_valid
+from openpilot.common.markdown import parse_markdown
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert
from openpilot.system.hardware import AGNOS, HARDWARE
@@ -89,7 +89,7 @@ def parse_release_notes(basedir: str) -> bytes:
with open(os.path.join(basedir, "RELEASES.md"), "rb") as f:
r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes
try:
- return bytes(MarkdownIt().render(r.decode("utf-8")), encoding="utf-8")
+ return bytes(parse_markdown(r.decode("utf-8")), encoding="utf-8")
except Exception:
return r + b"\n"
except FileNotFoundError: