diff --git a/cuteslides/__init__.py b/cuteslides/__init__.py index 55f4989..cd45e71 100644 --- a/cuteslides/__init__.py +++ b/cuteslides/__init__.py @@ -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) diff --git a/cuteslides/markdown.py b/cuteslides/markdown.py index 51106f2..d4e21ee 100644 --- a/cuteslides/markdown.py +++ b/cuteslides/markdown.py @@ -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("") @@ -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) diff --git a/tests/01_test_markdown.py b/tests/01_test_markdown.py index 7dcdaca..af09ef4 100644 --- a/tests/01_test_markdown.py +++ b/tests/01_test_markdown.py @@ -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(""" - Hello, World!""")).parse(), - [markdown.MarkdownSlide([ - markdown.MarkdownComment("This is a comment"), - markdown.MarkdownNewLine(), - markdown.MarkdownText("Hello, World!"), - ])], + markdown.MarkdownParser( + cleandoc( + """ + 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! - """)).parse(), - [markdown.MarkdownSlide([ - markdown.MarkdownText("Hello, World!"), - markdown.MarkdownComment("This is a comment"), - ])], + markdown.MarkdownParser( + cleandoc( + """Hello, World! + """ + ) + ) + .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"} + )