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>pull/214/head
							parent
							
								
									1289ebe9bd
								
							
						
					
					
						commit
						1aebe6ff6e
					
				
				 6 changed files with 84 additions and 6 deletions
			
			
		@ -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