feat(markdown): added presentation metadata

This commit is contained in:
Jordan ⌨️ 2024-02-18 01:01:52 +01:00
parent bcb3263149
commit 87c8e9e8cf
3 changed files with 289 additions and 88 deletions

View file

@ -15,5 +15,9 @@ def main(argv: list[str]) -> NoReturn:
print(f"{file} does not exist", file=sys.stderr)
exit(1)
MarkdownParser(file.read_text())
slides = MarkdownParser(file.read_text()).parse()
from pprint import pprint
pprint(slides)
exit(0)

View file

@ -11,19 +11,23 @@ Char = TypeVar("Char", str, bytes)
class MarkdownElement: ...
@dataclass
class MarkdownText(MarkdownElement):
content: str
@dataclass
class MarkdownComment(MarkdownElement):
content: str
@dataclass
class MarkdownHeader(MarkdownElement):
level: Literal[1, 2, 3, 4, 5]
content: str
@dataclass
class MarkdownListElement(MarkdownElement):
class ListType(StrEnum):
@ -34,23 +38,35 @@ class MarkdownListElement(MarkdownElement):
content: str
index: Optional[int]
@dataclass
class MarkdownCodeBlock(MarkdownElement):
lang: str
content: str
@dataclass
class MarkdownPreprocessor(MarkdownElement):
program: str
content: str
@dataclass
class MarkdownSlide(MarkdownElement):
elements: list[MarkdownElement]
@dataclass
class MarkdownNewLine(MarkdownElement):
...
class MarkdownNewLine(MarkdownElement): ...
# --- Presentation class ----------------------------------------------------
@dataclass
class Presentation:
slides: list[MarkdownSlide]
metadata: dict[str, str]
# --- Markdown parser ----------------------------------------------------------
@ -89,7 +105,7 @@ class MarkdownParser:
if char == c or char is None:
break
content += char
return content
def eat_line(self) -> str:
@ -98,13 +114,13 @@ class MarkdownParser:
def eat_until_pattern(self, pattern: str) -> str:
content = ""
while char := self.next():
if char is None:
if char is None:
break
if self.text[self.offset:].startswith(pattern):
if self.text[self.offset :].startswith(pattern):
self.offset += len(pattern)
break
content += char
return content
def eat_number(self) -> int:
@ -118,8 +134,9 @@ class MarkdownParser:
return int(nbr)
def parse(self) -> list[MarkdownSlide]:
def parse(self) -> Presentation:
slides: list[MarkdownSlide] = []
metadata: dict[str, str] = {}
current_slide: list[MarkdownElement] = []
while c := self.peek():
@ -133,16 +150,32 @@ class MarkdownParser:
case "-":
count = self.eat_count("-")
if count == 1:
self.eat(' ')
self.eat(" ")
content = self.eat_line()
current_slide.append(MarkdownListElement(MarkdownListElement.ListType.UNORDERED, content, None))
current_slide.append(
MarkdownListElement(
MarkdownListElement.ListType.UNORDERED, content, None
)
)
elif count == 3:
self.eat("\n")
slides.append(MarkdownSlide(current_slide.copy()))
current_slide.clear()
continue
if len(slides) == 0 and len(current_slide) == 0:
raw_metadata = self.eat_until_pattern("---").split("\n")
metadata = {
line.split(":")[0].strip(): line.split(":")[1].strip()
for line in raw_metadata
if line
}
self.eat("\n")
continue
else:
slides.append(MarkdownSlide(current_slide.copy()))
current_slide.clear()
continue
else:
current_slide.append(MarkdownText("-" * count + self.eat_line()))
current_slide.append(
MarkdownText("-" * count + self.eat_line())
)
case "`":
count = self.eat_count("`")
@ -151,17 +184,25 @@ class MarkdownParser:
content = self.eat_until_pattern("```")
current_slide.append(MarkdownCodeBlock(lang, content.strip()))
else:
current_slide.append(MarkdownText("`" * count + self.eat_line()))
current_slide.append(
MarkdownText("`" * count + self.eat_line())
)
case "*":
count = self.eat_count("*")
if count == 1 and self.peek() == " ":
self.eat(' ')
self.eat(" ")
content = self.eat_line()
current_slide.append(MarkdownListElement(MarkdownListElement.ListType.UNORDERED, content, None))
current_slide.append(
MarkdownListElement(
MarkdownListElement.ListType.UNORDERED, content, None
)
)
else:
current_slide.append(MarkdownText("*" * count + self.eat_line()))
current_slide.append(
MarkdownText("*" * count + self.eat_line())
)
case "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9":
index = self.eat_number()
@ -169,12 +210,16 @@ class MarkdownParser:
self.eat(".")
self.eat(" ")
content = self.eat_line()
current_slide.append(MarkdownListElement(MarkdownListElement.ListType.ORDERED, content, index))
current_slide.append(
MarkdownListElement(
MarkdownListElement.ListType.ORDERED, content, index
)
)
else:
current_slide.append(MarkdownText(str(count) + self.eat_line()))
case '<':
if self.text[self.offset:].startswith("<!--"):
case "<":
if self.text[self.offset :].startswith("<!--"):
self.offset += 4
self.eat(" ")
content = self.eat_until_pattern("-->")
@ -182,21 +227,23 @@ class MarkdownParser:
else:
current_slide.append(MarkdownText(self.eat_line()))
case '~':
case "~":
count = self.eat_count("~")
if count == 3:
program = self.eat_line()
content = self.eat_until_pattern("~~~")
current_slide.append(MarkdownPreprocessor(program, content))
else:
current_slide.append(MarkdownText("~" * count + self.eat_line()))
current_slide.append(
MarkdownText("~" * count + self.eat_line())
)
case "\n":
self.eat("\n")
current_slide.append(MarkdownNewLine())
case _:
current_slide.append(MarkdownText(self.eat_line()))
case _:
current_slide.append(MarkdownText(self.eat_line()))
slides.append(MarkdownSlide(current_slide))
return slides
return Presentation(slides, metadata)

View file

@ -2,131 +2,281 @@ import unittest
from cuteslides import markdown
from inspect import cleandoc
class TestMarkdown(unittest.TestCase):
maxDiff = None
def test_paragraphs(self):
self.assertEqual(
markdown.MarkdownParser("Hello, World!").parse(),
markdown.MarkdownParser("Hello, World!").parse().slides,
[markdown.MarkdownSlide([markdown.MarkdownText("Hello, World!")])],
)
self.assertEqual(
markdown.MarkdownParser("Hello,\nWorld!").parse(),
[markdown.MarkdownSlide([markdown.MarkdownText("Hello,"), markdown.MarkdownText("World!")])],
markdown.MarkdownParser("Hello,\nWorld!").parse().slides,
[
markdown.MarkdownSlide(
[markdown.MarkdownText("Hello,"), markdown.MarkdownText("World!")]
)
],
)
self.assertEqual(
markdown.MarkdownParser("Hello,\n\nWorld!").parse(),
[markdown.MarkdownSlide([markdown.MarkdownText("Hello,"), markdown.MarkdownNewLine(), markdown.MarkdownText("World!")])],
markdown.MarkdownParser("Hello,\n\nWorld!").parse().slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownText("Hello,"),
markdown.MarkdownNewLine(),
markdown.MarkdownText("World!"),
]
)
],
)
def test_headers(self):
self.assertEqual(
markdown.MarkdownParser("# Hello, World!").parse(),
markdown.MarkdownParser("# Hello, World!").parse().slides,
[markdown.MarkdownSlide([markdown.MarkdownHeader(1, "Hello, World!")])],
)
self.assertEqual(
markdown.MarkdownParser("## Hello, World!").parse(),
markdown.MarkdownParser("## Hello, World!").parse().slides,
[markdown.MarkdownSlide([markdown.MarkdownHeader(2, "Hello, World!")])],
)
self.assertEqual(
markdown.MarkdownParser("### Hello, World!").parse(),
markdown.MarkdownParser("### Hello, World!").parse().slides,
[markdown.MarkdownSlide([markdown.MarkdownHeader(3, "Hello, World!")])],
)
def test_lists(self):
self.assertEqual(
markdown.MarkdownParser(cleandoc("""- Milk
markdown.MarkdownParser(
cleandoc(
"""- Milk
- Bread
- Cheese""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Milk", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Bread", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Cheese", None),
])],
- Cheese"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Milk",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Bread",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Cheese",
None,
),
]
)
],
)
self.assertEqual(
markdown.MarkdownParser(cleandoc("""* Milk
markdown.MarkdownParser(
cleandoc(
"""* Milk
* Bread
* Cheese""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Milk", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Bread", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Cheese", None),
])],
* Cheese"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Milk",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Bread",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Cheese",
None,
),
]
)
],
)
self.assertEqual(
markdown.MarkdownParser(cleandoc("""1. Milk
markdown.MarkdownParser(
cleandoc(
"""1. Milk
2. Bread
3. Cheese""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.ORDERED, "Milk", 1),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.ORDERED, "Bread", 2),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.ORDERED, "Cheese", 3),
])],
3. Cheese"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.ORDERED, "Milk", 1
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.ORDERED, "Bread", 2
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.ORDERED, "Cheese", 3
),
]
)
],
)
def test_comments(self):
self.assertEqual(
markdown.MarkdownParser(cleandoc("""<!-- This is a comment -->
Hello, World!""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownComment("This is a comment"),
markdown.MarkdownNewLine(),
markdown.MarkdownText("Hello, World!"),
])],
markdown.MarkdownParser(
cleandoc(
"""<!-- This is a comment -->
Hello, World!"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownComment("This is a comment"),
markdown.MarkdownNewLine(),
markdown.MarkdownText("Hello, World!"),
]
)
],
)
self.assertEqual(
markdown.MarkdownParser(cleandoc("""Hello, World!
<!-- This is a comment -->""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownText("Hello, World!"),
markdown.MarkdownComment("This is a comment"),
])],
markdown.MarkdownParser(
cleandoc(
"""Hello, World!
<!-- This is a comment -->"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownText("Hello, World!"),
markdown.MarkdownComment("This is a comment"),
]
)
],
)
def test_code_blocks(self):
self.assertEqual(
markdown.MarkdownParser(cleandoc("""```python
markdown.MarkdownParser(
cleandoc(
"""```python
print("Hello, World!")
```""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownCodeBlock("python", 'print("Hello, World!")'),
])],
```"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownCodeBlock("python", 'print("Hello, World!")'),
]
)
],
)
def test_preprocessors(self):
self.assertEqual(
markdown.MarkdownParser(cleandoc("""~~~bash
markdown.MarkdownParser(
cleandoc(
"""~~~bash
echo 'Hello, World!'
~~~
Hi""")).parse(),
[markdown.MarkdownSlide([
markdown.MarkdownPreprocessor("bash", "echo 'Hello, World!'"),
markdown.MarkdownNewLine(),
markdown.MarkdownText("Hi"),
])],
Hi"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide(
[
markdown.MarkdownPreprocessor("bash", "echo 'Hello, World!'"),
markdown.MarkdownNewLine(),
markdown.MarkdownText("Hi"),
]
)
],
)
def test_slide(self):
self.assertEqual(
markdown.MarkdownParser(cleandoc("""# Hello, World!
markdown.MarkdownParser(
cleandoc(
"""# Hello, World!
---
- Milk
- Bread
- Cheese""")).parse(),
- Cheese"""
)
)
.parse()
.slides,
[
markdown.MarkdownSlide([markdown.MarkdownHeader(1, "Hello, World!")]),
markdown.MarkdownSlide([
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Milk", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Bread", None),
markdown.MarkdownListElement(markdown.MarkdownListElement.ListType.UNORDERED, "Cheese", None),
]),
markdown.MarkdownSlide(
[
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Milk",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Bread",
None,
),
markdown.MarkdownListElement(
markdown.MarkdownListElement.ListType.UNORDERED,
"Cheese",
None,
),
]
),
],
)
def test_metadata(self):
self.assertEqual(
markdown.MarkdownParser(
cleandoc(
"""---
title: Hello, World!
author: Keyb
date: 01 Jan 1970
---
"""
)
)
.parse()
.metadata,
{"title": "Hello, World!", "author": "Keyb", "date": "01 Jan 1970"}
)