Merge branch 'main' into feature/latex

This commit is contained in:
Tim Grelka
2024-12-11 00:53:09 +01:00
18 changed files with 352 additions and 39 deletions
+4
View File
@@ -160,3 +160,7 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
test/acceptanceTest.html
.DS_Store
.envrc
View File
-8
View File
@@ -1,8 +0,0 @@
import sys
import mistletoe
from fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
if __name__ == "__main__":
with open(sys.argv[1]) as fin, open(sys.argv[2], "w") as fout:
fout.write(mistletoe.markdown(fin, FiftyOhmHtmlRenderer))
+84 -23
View File
@@ -5,19 +5,36 @@ from mistletoe import HtmlRenderer
from renderer.comment import BlockComment, SpanComment
from renderer.dash import Dash
from renderer.halfwidth_spaces import HalfwidthSpaces
from renderer.morse import Morse
from renderer.nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots
from renderer.quote import Quote
from renderer.references import References
from renderer.tag import Tag
from renderer.underline import Underline
from renderer.unit import Unit
class FiftyOhmHtmlRenderer(HtmlRenderer):
margin_anchor_id = 0
margin_id = 0
section_url = "section.html"
ref_id = 0
def __init__(self):
super().__init__(Dash, BlockComment, SpanComment, Quote, Underline, Tag, HalfwidthSpaces, NonbreakingSpaces, NonbreakingSpacesDots)
super().__init__(
Dash,
BlockComment,
SpanComment,
Quote,
Unit,
Underline,
Morse,
Tag,
HalfwidthSpaces,
NonbreakingSpaces,
NonbreakingSpacesDots,
References,
)
def render_dash(self, token):
return " – "
@@ -34,15 +51,63 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
def render_underline(self, token):
return f"<u>{self.render_inner(token)}</u>"
@classmethod
def render_unit(cls, token: Unit):
unit = token.prefix + cls.convert_unit_helper(token.unit)
if token.unit in ["°", "%"]:
# Special cases with no space between value and unit.
return f"{token.value}{unit}"
else:
# Default case is rendered with a narrow no-break space.
return f"{token.value}&#8239;{unit}"
units = {
"Ohm": "Ω",
}
@classmethod
def convert_unit_helper(cls, unit: str) -> str:
"""Converts human-typable units to their preferred representation.
:param str unit: The unit to convert
"""
if unit in cls.units.keys():
return cls.units[unit]
else:
return unit
def render_thematic_break(self, token):
self.margin_anchor_id += 1
return f'<a id="margin_{self.margin_anchor_id}"></a>'
@staticmethod
def render_morse_helper(morse_code):
result = '<span class="morse">'
for char in morse_code:
result += '<span class="morse_char">\n'
for symbol in char:
result += '<span class="morse_char">\n'
if symbol == 1:
result += "▄"
elif symbol == 2:
result += "▄▄▄"
elif symbol == 3:
result += "&nbsp;"
result += "</span>\n"
result += "</span>\n"
result += "</span>"
return result
def render_morse(self, token):
morse_code = Morse.convert_to_morse_code(token.content)
return self.render_morse_helper(morse_code)
@staticmethod
def render_tag_helper(type, content, margin_id, margin_anchor_id):
""" This function is used to render the different types of tags. It is
used in the HtmlRenderer class and also in the test class """
return textwrap.dedent(f'''\
"""This function is used to render the different types of tags. It is
used in the HtmlRenderer class and also in the test class"""
return textwrap.dedent(f"""\
<div class="margin {type}" id="margin_for_{margin_id}">
{content}
</div>
@@ -62,33 +127,29 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
moveFunc();
}})();
</script>
''')
""")
def render_tag(self, token):
if(token.tagtype == "latexonly"):
if token.tagtype == "latexonly":
return ""
elif(token.tagtype == "webonly"):
elif token.tagtype == "webonly":
return self.render_inner(token)
self.margin_id += 1
if(token.tagtype == "webmargin"):
if token.tagtype == "webmargin":
type = "margin"
elif(token.tagtype == "webtip"):
elif token.tagtype == "webtip":
type = "tip"
elif(token.tagtype == "webindepth"):
elif token.tagtype == "webindepth":
type = "indepth"
else :
else:
type = token.tagtype
return self.render_tag_helper(
type,
self.render_inner(token),
self.margin_id,
self.margin_anchor_id
type, self.render_inner(token), self.margin_id, self.margin_anchor_id
)
def render_halfwidth_spaces(self, token):
return f"{token.first}.&#8239;{token.second}."
@@ -96,8 +157,8 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
return f"{token.first}&#160;{token.second}"
def render_nonbreaking_spaces_dots(self, token):
lookup = {
"" : "",
" " : "&#160;"
}
return f"{lookup[token.first]}{token.second}{lookup[token.third]}"
lookup = {"": "", " ": "&#160;"}
return f"{lookup[token.first]}{token.second}{lookup[token.third]}"
def render_references(self, token):
return f'<a href="{self.section_url}#ref_{token.first}" onclick="highlightRef(\'{token.first}\');">{self.ref_id}</a>'
+1 -1
View File
@@ -8,7 +8,7 @@ class HalfwidthSpaces(SpanToken):
Insert halfwidth space into span token (Z. B.)
Identifies "z.B.", "d.h." and "D.h." with and without space in between.
"""
pattern = re.compile(r"\b(z|d|D)\.(B|h)\.")
pattern = re.compile(r"\b(z|Z|d|D)\.\s?(B|h)\.")
def __init__(self, match_object):
self.first = match_object.group(1)
+89
View File
@@ -0,0 +1,89 @@
import re
from mistletoe.span_token import SpanToken
class Morse(SpanToken):
"""
Morse code span token ("[morse:moe]").
Generates morse code from a text.
"""
@staticmethod
def convert_to_morse_code(text):
dit = 1
dah = 2
space = 3
morse_code = {
"a": [dit, dah],
"b": [dah, dit, dit, dit],
"c": [dah, dit, dah, dit],
"d": [dah, dit, dit],
"e": [dit],
"f": [dit, dit, dah, dit],
"g": [dah, dah, dit],
"h": [dit, dit, dit, dit],
"i": [dit, dit],
"j": [dit, dah, dah, dah],
"k": [dah, dit, dah],
"l": [dit, dah, dit, dit],
"m": [dah, dah],
"n": [dah, dit],
"o": [dah, dah, dah],
"p": [dit, dah, dah, dit],
"q": [dah, dah, dit, dah],
"r": [dit, dah, dit],
"s": [dit, dit, dit],
"t": [dah],
"u": [dit, dit, dah],
"v": [dit, dit, dit, dah],
"w": [dit, dah, dah],
"x": [dah, dit, dit, dah],
"y": [dah, dit, dah, dah],
"z": [dah, dah, dit, dit],
"ä": [dit, dah, dit, dah],
"ö": [dah, dah, dah, dit],
"ü": [dit, dit, dah, dah],
"ß": [dit, dit, dit, dah, dah, dit, dit],
"0": [dah, dah, dah, dah, dah],
"1": [dit, dah, dah, dah, dah],
"2": [dit, dit, dah, dah, dah],
"3": [dit, dit, dit, dah, dah],
"4": [dit, dit, dit, dit, dah],
"5": [dit, dit, dit, dit, dit],
"6": [dah, dit, dit, dit, dit],
"7": [dah, dah, dit, dit, dit],
"8": [dah, dah, dah, dit, dit],
"9": [dah, dah, dah, dah, dit],
".": [dit, dah, dit, dah, dit, dah],
",": [dah, dah, dit, dit, dah, dah],
":": [dah, dah, dah, dit, dit, dit],
";": [dah, dit, dah, dit, dah, dit],
"?": [dit, dit, dah, dah, dit, dit],
"!": [dah, dit, dah, dit, dah, dah],
"-": [dah, dit, dit, dit, dit, dah],
"_": [dit, dit, dah, dah, dit, dah],
"(": [dah, dit, dah, dah, dit],
")": [dah, dit, dah, dah, dit, dah],
"'": [dit, dah, dah, dah, dah, dit],
"=": [dah, dit, dit, dit, dah],
"+": [dit, dah, dit, dah, dit],
"/": [dah, dit, dit, dah, dit],
"@": [dit, dah, dah, dit, dah, dit],
"ar": [dit, dah, dit, dah, dit],
"bk": [dah, dit, dit, dit, dah, dit, dah],
"sk": [dit, dit, dit, dah, dit, dah],
"correction": [dit, dit, dit, dit, dit, dit, dit, dit],
" ": [space]
}
result = []
for char in text:
if char.lower() in morse_code:
result.append(morse_code[char.lower()])
return result
parse_inner = False
pattern = re.compile(r'"\[morse:\s*(.+)\]"')
+1 -1
View File
@@ -6,7 +6,7 @@ from mistletoe.span_token import SpanToken
class NonbreakingSpaces(SpanToken):
"""
Insert unbreakable space into span token (§ 3)
Identifies "§$number", "Abs.$number", "Klasse [A,E,N]" w/ and w\ space between.
Identifies "§$number", "Abs.$number", "Klasse [A,E,N]" w/ and without space between.
"""
pattern = re.compile(r"(§|Abs\.|Klasse)\s?(\d|[NEA])")
+15
View File
@@ -0,0 +1,15 @@
import re
from mistletoe.span_token import SpanToken
class References(SpanToken):
"""
Resolve references for html
Identifies "[ref:$]"
"""
pattern = re.compile(r"\[ref\:(\S+)\]")
def __init__(self, match_object):
self.first = match_object.group(1)
+4 -4
View File
@@ -18,11 +18,11 @@ captures = [
"latexonly",
]
class Tag(BlockToken):
class Tag(BlockToken):
@staticmethod
def start(line):
return re.match(r"^\s*<("+"|".join(captures)+r")+>", line)
return re.match(r"^\s*<(" + "|".join(captures) + r")+>", line)
@classmethod
def read(cls, lines):
@@ -34,9 +34,9 @@ class Tag(BlockToken):
for line in lines:
if line.startswith(f"</{tagtype}>"):
break
else :
else:
child_lines.append(line)
return tagtype, tokenize(child_lines)
def __init__(self, match):
self.tagtype, self.children = match
self.tagtype, self.children = match
+55
View File
@@ -0,0 +1,55 @@
import re
from mistletoe.span_token import SpanToken
class Unit(SpanToken):
units = [
"A",
"Ah",
"A/mm²",
"baud",
"Bit",
"Bit/s",
"dB",
"dBi",
"dBm",
"dBW",
"F",
"J",
"Hz",
"H",
"cm",
"m",
"m²",
"Ohm",
"ppm",
"pps",
"s",
"V",
"W",
"Wh",
"°",
"%",
]
prefixes = ["p", "n", "μ", "m", "k", "M", "G"]
pattern = re.compile(
r"(?P<value>[0-9,]+) ?(?P<prefix>"
# Group all prefixes together in a capture group
# Empty string at end of list is required so the group always exists in the resulting match
+ "|".join(prefixes + [""])
+ ")(?P<unit>"
# Group all units together in a capture group.
+ "|".join(units)
+ r")(?!\w)"
)
value: str
prefix: str
unit: str
def __init__(self, match: re.Match):
self.value = match.group("value")
self.prefix = match.group("prefix") or ""
self.unit = match.group("unit")
+2
View File
@@ -94,6 +94,8 @@ CQ CQ hier ist DL1PZ mit einem allgemeinen Anruf, hier ist DL1PZ und hört.
[table:n_morsetelegrafie_morsecode_buchstaben:Morsecode (Buchstaben)]
</webmargin>
Das ist ein A in Morsecode: [morse:a], das finde ich ganz toll!
<webtip>
...
</webtip>
+1
View File
@@ -11,6 +11,7 @@ def test_halfwidth_space_html():
assert mistletoe.markdown(input, FiftyOhmHtmlRenderer) == paragraph(target)
def test_halfwidth_space_latex():
input = "Dies ist z.B. ein Test. D.h. hier sollten d.h. Spaces ersetzt worden sein."
target = "\nDies ist z.\,B. ein Test. D.\,h. hier sollten d.\,h. Spaces ersetzt worden sein.\n"
+14
View File
@@ -0,0 +1,14 @@
import mistletoe
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
def test_html(capsys) :
with capsys.disabled():
with open("test/acceptanceTest.md") as file:
content = file.read()
output = mistletoe.markdown(content, FiftyOhmHtmlRenderer)
with open("test/acceptanceTest.html", "w") as output_file:
output_file.write(output)
+17
View File
@@ -0,0 +1,17 @@
import mistletoe
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
def test_morse_html() :
assertions = {
'"[morse:a]"' : "<p>"+FiftyOhmHtmlRenderer.render_morse_helper([[1,2]])+"</p>\n",
'"[morse: a]"' : "<p>"+FiftyOhmHtmlRenderer.render_morse_helper([[1,2]])+"</p>\n",
'"[morse: aaa]"' : "<p>"+FiftyOhmHtmlRenderer.render_morse_helper([[1,2],[1,2],[1,2]])+"</p>\n",
'"[morse: a a]"' : "<p>"+FiftyOhmHtmlRenderer.render_morse_helper([[1,2],[3],[1,2]])+"</p>\n",
'"[morse: db0]"' : "<p>"+FiftyOhmHtmlRenderer.render_morse_helper([[2,1,1],[2,1,1,1],[2,2,2,2,2]])+"</p>\n",
}
for assertion in assertions:
assert mistletoe.markdown(assertion, FiftyOhmHtmlRenderer) == assertions[assertion]
+17
View File
@@ -0,0 +1,17 @@
import mistletoe
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from test.util import paragraph
def test_references_html():
# Right now this tests only the default values.
# The correct behavior can be tested when the information is added to the renderer class
assertions = {
"[ref:e_oszilloskop_bildschirmfoto_sinus]": '<a href="section.html#ref_e_oszilloskop_bildschirmfoto_sinus" onclick="highlightRef(\'e_oszilloskop_bildschirmfoto_sinus\');">0</a>',
"[ref:n_rst_r]": '<a href="section.html#ref_n_rst_r" onclick="highlightRef(\'n_rst_r\');">0</a>',
"[ref:Uuuuuh]": '<a href="section.html#ref_Uuuuuh" onclick="highlightRef(\'Uuuuuh\');">0</a>', # Test the default parameters
}
for key, value in assertions.items():
assert mistletoe.markdown(key, FiftyOhmHtmlRenderer) == paragraph(value)
+1
View File
@@ -4,6 +4,7 @@ from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from renderer.fifty_ohm_latex_renderer import FiftyOhmLaTeXRenderer
def test_thematic_break_html() :
assertions = {
"---" : '<a id="margin_1"></a>\n',
+45
View File
@@ -0,0 +1,45 @@
import mistletoe
import pytest
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from .util import paragraph
@pytest.mark.html
def test_basic_units_html():
"""Test basic units: Insert non-breaking, smaller space."""
assertions = {
"1 V": "1&#8239;V",
"0,12 V": "0,12&#8239;V",
"0,75 kW": "0,75&#8239;kW",
"33 A": "33&#8239;A",
"1 Ah": "1&#8239;Ah", # Ah can be a tricky case if the RegEx is wrong, as A might match before Ah, thus rendering the output with an "A" unit
}
for input, output in assertions.items():
assert mistletoe.markdown(input, FiftyOhmHtmlRenderer) == paragraph(output)
@pytest.mark.html
def test_translated_units_html():
"""Test translated units: Replace Ohm with Ω."""
assertions = {
"50 Ohm": "50&#8239;Ω",
"100 mOhm": "100&#8239;mΩ",
}
for input, output in assertions.items():
assert mistletoe.markdown(input, FiftyOhmHtmlRenderer) == paragraph(output)
@pytest.mark.html
def test_special_units_html():
"""Test special units: Don't put space before ° and %."""
assertions = {
"100%": "100%",
"42,24°": "42,24°",
}
for input, output in assertions.items():
assert mistletoe.markdown(input, FiftyOhmHtmlRenderer) == paragraph(output)
+2 -2
View File
@@ -1,2 +1,2 @@
def paragraph(text) :
return f'<p>{text}</p>\n'
def paragraph(text):
return f"<p>{text}</p>\n"