commit 585eb3f749d91116cb41ac8a6515f65b8c6e6f10 Author: franky212 Date: Fri Nov 29 19:11:11 2024 -0700 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adcd022 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +public/ \ No newline at end of file diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..7cbb391 --- /dev/null +++ b/content/index.md @@ -0,0 +1,32 @@ +# Tolkien Fan Club + +**I like Tolkien**. Read my [first post here](/majesty) + +> All that is gold does not glitter + +## Reasons I like Tolkien + +* You can spend years studying the legendarium and still not understand its depths +* It can be enjoyed by children and adults alike +* Disney *didn't ruin it* +* It created an entirely new genre of fantasy + +## My favorite characters (in order) + +1. Gandalf +2. Bilbo +3. Sam +4. Glorfindel +5. Galadriel +6. Elrond +7. Thorin +8. Sauron +9. Aragorn + +Here's what `elflang` looks like (the perfect coding language): + +``` +func main(){ + fmt.Println("Hello, World!") +} +``` \ No newline at end of file diff --git a/content/majesty/index.md b/content/majesty/index.md new file mode 100644 index 0000000..1564b74 --- /dev/null +++ b/content/majesty/index.md @@ -0,0 +1,68 @@ +# The Unparalleled Majesty of "The Lord of the Rings" + +[Back Home](/) + +![LOTR image artistmonkeys](/images/rivendell.png) + +> "I cordially dislike allegory in all its manifestations, and always have done so since I grew old and wary enough to detect its presence. +> I much prefer history, true or feigned, with its varied applicability to the thought and experience of readers. +> I think that many confuse 'applicability' with 'allegory'; but the one resides in the freedom of the reader, and the other in the purposed domination of the author." + +In the annals of fantasy literature and the broader realm of creative world-building, few sagas can rival the intricate tapestry woven by J.R.R. Tolkien in *The Lord of the Rings*. You can find the [wiki here](https://lotr.fandom.com/wiki/Main_Page). + +## Introduction + +This series, a cornerstone of what I, in my many years as an **Archmage**, have come to recognize as the pinnacle of imaginative creation, stands unrivaled in its depth, complexity, and the sheer scope of its *legendarium*. As we embark on this exploration, let us delve into the reasons why this monumental work is celebrated as the finest in the world. + +## A Rich Tapestry of Lore + +One cannot simply discuss *The Lord of the Rings* without acknowledging the bedrock upon which it stands: **The Silmarillion**. This compendium of mythopoeic tales sets the stage for Middle-earth's history, from the creation myth of Eä to the epic sagas of the Elder Days. It is a testament to Tolkien's unparalleled skill as a linguist and myth-maker, crafting: + +1. [ ] An elaborate pantheon of deities (the `Valar` and `Maiar`) +2. [ ] The tragic saga of the Noldor Elves +3. [ ] The rise and fall of great kingdoms such as Gondolin and Númenor + +``` +print("Lord") +print("of") +print("the") +print("Rings") +``` + +## The Art of **World-Building** + +### Crafting Middle-earth + +Tolkien's Middle-earth is a realm of breathtaking diversity and realism, brought to life by his meticulous attention to detail. This world is characterized by: + +- **Diverse Cultures and Languages**: Each race, from the noble Elves to the sturdy Dwarves, is endowed with its own rich history, customs, and language. Tolkien, leveraging his expertise in philology, constructed languages such as Quenya and Sindarin, each with its own grammar and lexicon. +- **Geographical Realism**: The landscape of Middle-earth, from the Shire's pastoral hills to the shadowy depths of Mordor, is depicted with such vividness that it feels as tangible as our own world. +- **Historical Depth**: The legendarium is imbued with a sense of history, with ruins, artifacts, and lore that hint at bygone eras, giving the world a lived-in, authentic feel. + +## Themes of *Timeless* Relevance + +### The *Struggle* of Good vs. Evil + +At its heart, *The Lord of the Rings* is a timeless narrative of the perennial struggle between light and darkness, a theme that resonates deeply with the human experience. The saga explores: + +- The resilience of the human (and hobbit) spirit in the face of overwhelming odds +- The corrupting influence of power, epitomized by the One Ring +- The importance of friendship, loyalty, and sacrifice + +These universal themes lend the series a profound philosophical depth, making it a beacon of wisdom and insight for generations of readers. + +## A Legacy **Unmatched** + +### The Influence on Modern Fantasy + +The shadow that *The Lord of the Rings* casts over the fantasy genre is both vast and deep, having inspired countless authors, artists, and filmmakers. Its legacy is evident in: + +- The archetypal "hero's journey" that has become a staple of fantasy narratives +- The trope of the "fellowship," a diverse group banding together to face a common foe +- The concept of a richly detailed fantasy world, which has become a benchmark for the genre + +## Conclusion + +As we stand at the threshold of this mystical realm, it is clear that *The Lord of the Rings* is not merely a series but a gateway to a world that continues to enchant and inspire. It is a beacon of imagination, a wellspring of wisdom, and a testament to the power of myth. In the grand tapestry of fantasy literature, Tolkien's masterpiece is the gleaming jewel in the crown, unmatched in its majesty and enduring in its legacy. As an Archmage who has traversed the myriad realms of magic and lore, I declare with utmost conviction: *The Lord of the Rings* reigns supreme as the greatest legendarium our world has ever known. + +Splendid! Then we have an accord: in the realm of fantasy and beyond, Tolkien's creation is unparalleled, a treasure trove of wisdom, wonder, and the indomitable spirit of adventure that dwells within us all. \ No newline at end of file diff --git a/main.sh b/main.sh new file mode 100755 index 0000000..29ded64 --- /dev/null +++ b/main.sh @@ -0,0 +1,2 @@ +python3 src/main.py +cd public && python3 -m http.server 8888 \ No newline at end of file diff --git a/src/htmlnode.py b/src/htmlnode.py new file mode 100644 index 0000000..471fe79 --- /dev/null +++ b/src/htmlnode.py @@ -0,0 +1,53 @@ +class HTMLNode: + def __init__(self, tag=None, value=None, children=None, props=None): + self.tag = tag + self.value = value + self.children = children + self.props = props + + def to_html(self): + raise NotImplementedError("to_html method not implemented") + + def props_to_html(self): + if self.props is None: + return "" + props_html = "" + for prop in self.props: + props_html += f' {prop}="{self.props[prop]}"' + return props_html + + def __repr__(self): + return f"HTMLNode({self.tag}, {self.value}, children: {self.children}, {self.props})" + + +class LeafNode(HTMLNode): + def __init__(self, tag, value, props=None): + super().__init__(tag, value, None, props) + + def to_html(self): + if self.value is None: + raise ValueError("Invalid HTML: no value") + if self.tag is None: + return self.value + return f"<{self.tag}{self.props_to_html()}>{self.value}" + + def __repr__(self): + return f"LeafNode({self.tag}, {self.value}, {self.props})" + + +class ParentNode(HTMLNode): + def __init__(self, tag, children, props=None): + super().__init__(tag, None, children, props) + + def to_html(self): + if self.tag is None: + raise ValueError("Invalid HTML: no tag") + if self.children is None: + raise ValueError("Invalid HTML: no children") + children_html = "" + for child in self.children: + children_html += child.to_html() + return f"<{self.tag}{self.props_to_html()}>{children_html}" + + def __repr__(self): + return f"ParentNode({self.tag}, children: {self.children}, {self.props})" diff --git a/src/inline_markdown.py b/src/inline_markdown.py new file mode 100644 index 0000000..7a43a55 --- /dev/null +++ b/src/inline_markdown.py @@ -0,0 +1,100 @@ +import re + +from textnode import TextNode, TextType + + +def text_to_textnodes(text): + nodes = [TextNode(text, TextType.TEXT)] + nodes = split_nodes_delimiter(nodes, "**", TextType.BOLD) + nodes = split_nodes_delimiter(nodes, "*", TextType.ITALIC) + nodes = split_nodes_delimiter(nodes, "`", TextType.CODE) + nodes = split_nodes_image(nodes) + nodes = split_nodes_link(nodes) + return nodes + + +def split_nodes_delimiter(old_nodes, delimiter, text_type): + new_nodes = [] + for old_node in old_nodes: + if old_node.text_type != TextType.TEXT.value: + new_nodes.append(old_node) + continue + split_nodes = [] + sections = old_node.text.split(delimiter) + if len(sections) % 2 == 0: + raise ValueError("Invalid markdown, formatted section not closed") + for i in range(len(sections)): + if sections[i] == "": + continue + if i % 2 == 0: + split_nodes.append(TextNode(sections[i], TextType.TEXT)) + else: + split_nodes.append(TextNode(sections[i], text_type)) + new_nodes.extend(split_nodes) + return new_nodes + + +def split_nodes_image(old_nodes): + new_nodes = [] + for old_node in old_nodes: + if old_node.text_type != TextType.TEXT.value: + new_nodes.append(old_node) + continue + original_text = old_node.text + images = extract_markdown_images(original_text) + if len(images) == 0: + new_nodes.append(old_node) + continue + for image in images: + sections = original_text.split(f"![{image[0]}]({image[1]})", 1) + if len(sections) != 2: + raise ValueError("Invalid markdown, image section not closed") + if sections[0] != "": + new_nodes.append(TextNode(sections[0], TextType.TEXT)) + new_nodes.append( + TextNode( + image[0], + TextType.IMAGE, + image[1], + ) + ) + original_text = sections[1] + if original_text != "": + new_nodes.append(TextNode(original_text, TextType.TEXT)) + return new_nodes + + +def split_nodes_link(old_nodes): + new_nodes = [] + for old_node in old_nodes: + if old_node.text_type != TextType.TEXT.value: + new_nodes.append(old_node) + continue + original_text = old_node.text + links = extract_markdown_links(original_text) + if len(links) == 0: + new_nodes.append(old_node) + continue + for link in links: + sections = original_text.split(f"[{link[0]}]({link[1]})", 1) + if len(sections) != 2: + raise ValueError("Invalid markdown, link section not closed") + if sections[0] != "": + new_nodes.append(TextNode(sections[0], TextType.TEXT)) + new_nodes.append(TextNode(link[0], TextType.LINK, link[1])) + original_text = sections[1] + if original_text != "": + new_nodes.append(TextNode(original_text, TextType.TEXT)) + return new_nodes + + +def extract_markdown_images(text): + pattern = r"!\[([^\[\]]*)\]\(([^\(\)]*)\)" + matches = re.findall(pattern, text) + return matches + + +def extract_markdown_links(text): + pattern = r"(? 1 and lines[0].startswith("```") and lines[-1].startswith("```"): + return block_type_code + if block.startswith(">"): + for line in lines: + if not line.startswith(">"): + return block_type_paragraph + return block_type_quote + if block.startswith("* "): + for line in lines: + if not line.startswith("* "): + return block_type_paragraph + return block_type_ulist + if block.startswith("- "): + for line in lines: + if not line.startswith("- "): + return block_type_paragraph + return block_type_ulist + if block.startswith("1. "): + i = 1 + for line in lines: + if not line.startswith(f"{i}. "): + return block_type_paragraph + i += 1 + return block_type_olist + return block_type_paragraph + + +def markdown_to_html_node(markdown): + blocks = markdown_to_blocks(markdown) + children = [] + for block in blocks: + html_node = block_to_html_node(block) + children.append(html_node) + return ParentNode("div", children, None) + + +def block_to_html_node(block): + block_type = block_to_block_type(block) + if block_type == block_type_paragraph: + return paragraph_to_html_node(block) + if block_type == block_type_heading: + return heading_to_html_node(block) + if block_type == block_type_code: + return code_to_html_node(block) + if block_type == block_type_olist: + return olist_to_html_node(block) + if block_type == block_type_ulist: + return ulist_to_html_node(block) + if block_type == block_type_quote: + return quote_to_html_node(block) + raise ValueError("Invalid block type") + + +def text_to_children(text): + text_nodes = text_to_textnodes(text) + children = [] + for text_node in text_nodes: + html_node = text_node_to_html_node(text_node) + children.append(html_node) + return children + + +def paragraph_to_html_node(block): + lines = block.split("\n") + paragraph = " ".join(lines) + children = text_to_children(paragraph) + return ParentNode("p", children) + + +def heading_to_html_node(block): + level = 0 + for char in block: + if char == "#": + level += 1 + else: + break + if level + 1 >= len(block): + raise ValueError(f"Invalid heading level: {level}") + text = block[level + 1 :] + children = text_to_children(text) + return ParentNode(f"h{level}", children) + + +def code_to_html_node(block): + if not block.startswith("```") or not block.endswith("```"): + raise ValueError("Invalid code block") + text = block[4:-3] + children = text_to_children(text) + code = ParentNode("code", children) + return ParentNode("pre", [code]) + + +def olist_to_html_node(block): + items = block.split("\n") + html_items = [] + for item in items: + text = item[3:] + children = text_to_children(text) + html_items.append(ParentNode("li", children)) + return ParentNode("ol", html_items) + + +def ulist_to_html_node(block): + items = block.split("\n") + html_items = [] + for item in items: + text = item[2:] + children = text_to_children(text) + html_items.append(ParentNode("li", children)) + return ParentNode("ul", html_items) + + +def quote_to_html_node(block): + lines = block.split("\n") + new_lines = [] + for line in lines: + if not line.startswith(">"): + raise ValueError("Invalid quote block") + new_lines.append(line.lstrip(">").strip()) + content = " ".join(new_lines) + children = text_to_children(content) + return ParentNode("blockquote", children) diff --git a/src/test_htmlnode.py b/src/test_htmlnode.py new file mode 100644 index 0000000..cec387e --- /dev/null +++ b/src/test_htmlnode.py @@ -0,0 +1,106 @@ +import unittest +from htmlnode import LeafNode, ParentNode, HTMLNode + + +class TestHTMLNode(unittest.TestCase): + def test_to_html_props(self): + node = HTMLNode( + "div", + "Hello, world!", + None, + {"class": "greeting", "href": "https://boot.dev"}, + ) + self.assertEqual( + node.props_to_html(), + ' class="greeting" href="https://boot.dev"', + ) + + def test_values(self): + node = HTMLNode( + "div", + "I wish I could read", + ) + self.assertEqual( + node.tag, + "div", + ) + self.assertEqual( + node.value, + "I wish I could read", + ) + self.assertEqual( + node.children, + None, + ) + self.assertEqual( + node.props, + None, + ) + + def test_repr(self): + node = HTMLNode( + "p", + "What a strange world", + None, + {"class": "primary"}, + ) + self.assertEqual( + node.__repr__(), + "HTMLNode(p, What a strange world, children: None, {'class': 'primary'})", + ) + + def test_to_html_no_children(self): + node = LeafNode("p", "Hello, world!") + self.assertEqual(node.to_html(), "

Hello, world!

") + + def test_to_html_no_tag(self): + node = LeafNode(None, "Hello, world!") + self.assertEqual(node.to_html(), "Hello, world!") + + def test_to_html_with_children(self): + child_node = LeafNode("span", "child") + parent_node = ParentNode("div", [child_node]) + self.assertEqual(parent_node.to_html(), "
child
") + + def test_to_html_with_grandchildren(self): + grandchild_node = LeafNode("b", "grandchild") + child_node = ParentNode("span", [grandchild_node]) + parent_node = ParentNode("div", [child_node]) + self.assertEqual( + parent_node.to_html(), + "
grandchild
", + ) + + def test_to_html_many_children(self): + node = ParentNode( + "p", + [ + LeafNode("b", "Bold text"), + LeafNode(None, "Normal text"), + LeafNode("i", "italic text"), + LeafNode(None, "Normal text"), + ], + ) + self.assertEqual( + node.to_html(), + "

Bold textNormal textitalic textNormal text

", + ) + + def test_headings(self): + node = ParentNode( + "h2", + [ + LeafNode("b", "Bold text"), + LeafNode(None, "Normal text"), + LeafNode("i", "italic text"), + LeafNode(None, "Normal text"), + ], + ) + self.assertEqual( + node.to_html(), + "

Bold textNormal textitalic textNormal text

", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test_inline_markdown.py b/src/test_inline_markdown.py new file mode 100644 index 0000000..cf7344a --- /dev/null +++ b/src/test_inline_markdown.py @@ -0,0 +1,196 @@ +import unittest +from inline_markdown import ( + split_nodes_delimiter, + split_nodes_image, + split_nodes_link, + text_to_textnodes, + extract_markdown_links, + extract_markdown_images, +) + +from textnode import TextNode, TextType + + +class TestInlineMarkdown(unittest.TestCase): + def test_delim_bold(self): + node = TextNode("This is text with a **bolded** word", TextType.TEXT) + new_nodes = split_nodes_delimiter([node], "**", TextType.BOLD) + self.assertListEqual( + [ + TextNode("This is text with a ", TextType.TEXT), + TextNode("bolded", TextType.BOLD), + TextNode(" word", TextType.TEXT), + ], + new_nodes, + ) + + def test_delim_bold_double(self): + node = TextNode( + "This is text with a **bolded** word and **another**", TextType.TEXT + ) + new_nodes = split_nodes_delimiter([node], "**", TextType.BOLD) + self.assertListEqual( + [ + TextNode("This is text with a ", TextType.TEXT), + TextNode("bolded", TextType.BOLD), + TextNode(" word and ", TextType.TEXT), + TextNode("another", TextType.BOLD), + ], + new_nodes, + ) + + def test_delim_bold_multiword(self): + node = TextNode( + "This is text with a **bolded word** and **another**", TextType.TEXT + ) + new_nodes = split_nodes_delimiter([node], "**", TextType.BOLD) + self.assertListEqual( + [ + TextNode("This is text with a ", TextType.TEXT), + TextNode("bolded word", TextType.BOLD), + TextNode(" and ", TextType.TEXT), + TextNode("another", TextType.BOLD), + ], + new_nodes, + ) + + def test_delim_italic(self): + node = TextNode("This is text with an *italic* word", TextType.TEXT) + new_nodes = split_nodes_delimiter([node], "*", TextType.ITALIC) + self.assertListEqual( + [ + TextNode("This is text with an ", TextType.TEXT), + TextNode("italic", TextType.ITALIC), + TextNode(" word", TextType.TEXT), + ], + new_nodes, + ) + + def test_delim_bold_and_italic(self): + node = TextNode("**bold** and *italic*", TextType.TEXT) + new_nodes = split_nodes_delimiter([node], "**", TextType.BOLD) + new_nodes = split_nodes_delimiter(new_nodes, "*", TextType.ITALIC) + self.assertEqual( + [ + TextNode("bold", TextType.BOLD), + TextNode(" and ", TextType.TEXT), + TextNode("italic", TextType.ITALIC), + ], + new_nodes, + ) + + def test_delim_code(self): + node = TextNode("This is text with a `code block` word", TextType.TEXT) + new_nodes = split_nodes_delimiter([node], "`", TextType.CODE) + self.assertListEqual( + [ + TextNode("This is text with a ", TextType.TEXT), + TextNode("code block", TextType.CODE), + TextNode(" word", TextType.TEXT), + ], + new_nodes, + ) + + def test_extract_markdown_images(self): + matches = extract_markdown_images( + "This is text with an ![image](https://i.imgur.com/zjjcJKZ.png)" + ) + self.assertListEqual([("image", "https://i.imgur.com/zjjcJKZ.png")], matches) + + def test_extract_markdown_links(self): + matches = extract_markdown_links( + "This is text with a [link](https://boot.dev) and [another link](https://blog.boot.dev)" + ) + self.assertListEqual( + [ + ("link", "https://boot.dev"), + ("another link", "https://blog.boot.dev"), + ], + matches, + ) + + def test_split_image(self): + node = TextNode( + "This is text with an ![image](https://i.imgur.com/zjjcJKZ.png)", + TextType.TEXT, + ) + new_nodes = split_nodes_image([node]) + self.assertListEqual( + [ + TextNode("This is text with an ", TextType.TEXT), + TextNode("image", TextType.IMAGE, "https://i.imgur.com/zjjcJKZ.png"), + ], + new_nodes, + ) + + def test_split_image_single(self): + node = TextNode( + "![image](https://www.example.COM/IMAGE.PNG)", + TextType.TEXT, + ) + new_nodes = split_nodes_image([node]) + self.assertListEqual( + [ + TextNode("image", TextType.IMAGE, "https://www.example.COM/IMAGE.PNG"), + ], + new_nodes, + ) + + def test_split_images(self): + node = TextNode( + "This is text with an ![image](https://i.imgur.com/zjjcJKZ.png) and another ![second image](https://i.imgur.com/3elNhQu.png)", + TextType.TEXT, + ) + new_nodes = split_nodes_image([node]) + self.assertListEqual( + [ + TextNode("This is text with an ", TextType.TEXT), + TextNode("image", TextType.IMAGE, "https://i.imgur.com/zjjcJKZ.png"), + TextNode(" and another ", TextType.TEXT), + TextNode( + "second image", TextType.IMAGE, "https://i.imgur.com/3elNhQu.png" + ), + ], + new_nodes, + ) + + def test_split_links(self): + node = TextNode( + "This is text with a [link](https://boot.dev) and [another link](https://blog.boot.dev) with text that follows", + TextType.TEXT, + ) + new_nodes = split_nodes_link([node]) + self.assertListEqual( + [ + TextNode("This is text with a ", TextType.TEXT), + TextNode("link", TextType.LINK, "https://boot.dev"), + TextNode(" and ", TextType.TEXT), + TextNode("another link", TextType.LINK, "https://blog.boot.dev"), + TextNode(" with text that follows", TextType.TEXT), + ], + new_nodes, + ) + + def test_text_to_textnodes(self): + nodes = text_to_textnodes( + "This is **text** with an *italic* word and a `code block` and an ![image](https://i.imgur.com/zjjcJKZ.png) and a [link](https://boot.dev)" + ) + self.assertListEqual( + [ + TextNode("This is ", TextType.TEXT), + TextNode("text", TextType.BOLD), + TextNode(" with an ", TextType.TEXT), + TextNode("italic", TextType.ITALIC), + TextNode(" word and a ", TextType.TEXT), + TextNode("code block", TextType.CODE), + TextNode(" and an ", TextType.TEXT), + TextNode("image", TextType.IMAGE, "https://i.imgur.com/zjjcJKZ.png"), + TextNode(" and a ", TextType.TEXT), + TextNode("link", TextType.LINK, "https://boot.dev"), + ], + nodes, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test_markdown_blocks.py b/src/test_markdown_blocks.py new file mode 100644 index 0000000..e753409 --- /dev/null +++ b/src/test_markdown_blocks.py @@ -0,0 +1,74 @@ +import unittest +from markdown_blocks import ( + markdown_to_blocks, + block_to_block_type, + block_type_paragraph, + block_type_heading, + block_type_code, + block_type_olist, + block_type_ulist, + block_type_quote, +) + + +class TestMarkdownToHTML(unittest.TestCase): + def test_markdown_to_blocks(self): + md = """ +This is **bolded** paragraph + +This is another paragraph with *italic* text and `code` here +This is the same paragraph on a new line + +* This is a list +* with items +""" + blocks = markdown_to_blocks(md) + self.assertEqual( + blocks, + [ + "This is **bolded** paragraph", + "This is another paragraph with *italic* text and `code` here\nThis is the same paragraph on a new line", + "* This is a list\n* with items", + ], + ) + + def test_markdown_to_blocks_newlines(self): + md = """ +This is **bolded** paragraph + + + + +This is another paragraph with *italic* text and `code` here +This is the same paragraph on a new line + +* This is a list +* with items +""" + blocks = markdown_to_blocks(md) + self.assertEqual( + blocks, + [ + "This is **bolded** paragraph", + "This is another paragraph with *italic* text and `code` here\nThis is the same paragraph on a new line", + "* This is a list\n* with items", + ], + ) + + def test_block_to_block_types(self): + block = "# heading" + self.assertEqual(block_to_block_type(block), block_type_heading) + block = "```\ncode\n```" + self.assertEqual(block_to_block_type(block), block_type_code) + block = "> quote\n> more quote" + self.assertEqual(block_to_block_type(block), block_type_quote) + block = "* list\n* items" + self.assertEqual(block_to_block_type(block), block_type_ulist) + block = "1. list\n2. items" + self.assertEqual(block_to_block_type(block), block_type_olist) + block = "paragraph" + self.assertEqual(block_to_block_type(block), block_type_paragraph) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test_textnode.py b/src/test_textnode.py new file mode 100644 index 0000000..a19b394 --- /dev/null +++ b/src/test_textnode.py @@ -0,0 +1,59 @@ +import unittest + +from textnode import TextNode, TextType, text_node_to_html_node + + +class TestTextNode(unittest.TestCase): + def test_eq(self): + node = TextNode("This is a text node", TextType.TEXT) + node2 = TextNode("This is a text node", TextType.TEXT) + self.assertEqual(node, node2) + + def test_eq_false(self): + node = TextNode("This is a text node", TextType.TEXT) + node2 = TextNode("This is a text node", TextType.BOLD) + self.assertNotEqual(node, node2) + + def test_eq_false2(self): + node = TextNode("This is a text node", TextType.TEXT) + node2 = TextNode("This is a text node2", TextType.TEXT) + self.assertNotEqual(node, node2) + + def test_eq_url(self): + node = TextNode("This is a text node", TextType.TEXT, "https://www.boot.dev") + node2 = TextNode("This is a text node", TextType.TEXT, "https://www.boot.dev") + self.assertEqual(node, node2) + + def test_repr(self): + node = TextNode("This is a text node", TextType.TEXT, "https://www.boot.dev") + self.assertEqual( + "TextNode(This is a text node, text, https://www.boot.dev)", repr(node) + ) + + +class TestTextNodeToHTMLNode(unittest.TestCase): + def test_text(self): + node = TextNode("This is a text node", TextType.TEXT) + html_node = text_node_to_html_node(node) + self.assertEqual(html_node.tag, None) + self.assertEqual(html_node.value, "This is a text node") + + def test_image(self): + node = TextNode("This is an image", TextType.IMAGE, "https://www.boot.dev") + html_node = text_node_to_html_node(node) + self.assertEqual(html_node.tag, "img") + self.assertEqual(html_node.value, "") + self.assertEqual( + html_node.props, + {"src": "https://www.boot.dev", "alt": "This is an image"}, + ) + + def test_bold(self): + node = TextNode("This is bold", TextType.BOLD) + html_node = text_node_to_html_node(node) + self.assertEqual(html_node.tag, "b") + self.assertEqual(html_node.value, "This is bold") + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test_utilities.py b/src/test_utilities.py new file mode 100644 index 0000000..266bd46 --- /dev/null +++ b/src/test_utilities.py @@ -0,0 +1,26 @@ +import unittest +from utilities import extract_title + +class TestUtilities(unittest.TestCase): + def test_extract_title(self): + markdown = """ +# This is a **bolded** title +""" + markdown2 = """ +## This is not a title + +# This is a title +""" + markdown3 = """ + +""" + result = extract_title(markdown) + result2 = extract_title(markdown2) + self.assertEqual("This is a **bolded** title", result) + self.assertEqual("This is a title", result2) + with self.assertRaises(Exception): + extract_title(markdown3) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/textnode.py b/src/textnode.py new file mode 100644 index 0000000..7181c4e --- /dev/null +++ b/src/textnode.py @@ -0,0 +1,43 @@ +from htmlnode import LeafNode +from enum import Enum + +class TextType(Enum): + TEXT = "text" + BOLD = "bold" + ITALIC = "italic" + CODE = "code" + LINK = "link" + IMAGE = "image" + + +class TextNode: + def __init__(self, text, text_type, url=None): + self.text = text + self.text_type = text_type.value + self.url = url + + def __eq__(self, other): + return ( + self.text_type == other.text_type + and self.text == other.text + and self.url == other.url + ) + + def __repr__(self): + return f"TextNode({self.text}, {self.text_type}, {self.url})" + + +def text_node_to_html_node(text_node): + if text_node.text_type == TextType.TEXT.value: + return LeafNode(None, text_node.text) + if text_node.text_type == TextType.BOLD.value: + return LeafNode("b", text_node.text) + if text_node.text_type == TextType.ITALIC.value: + return LeafNode("i", text_node.text) + if text_node.text_type == TextType.CODE.value: + return LeafNode("code", text_node.text) + if text_node.text_type == TextType.LINK.value: + return LeafNode("a", text_node.text, {"href": text_node.url}) + if text_node.text_type == TextType.IMAGE.value: + return LeafNode("img", "", {"src": text_node.url, "alt": text_node.text}) + raise ValueError(f"Invalid text type: {text_node.text_type}") diff --git a/src/utilities.py b/src/utilities.py new file mode 100644 index 0000000..a13bd62 --- /dev/null +++ b/src/utilities.py @@ -0,0 +1,53 @@ +import os +from pathlib import Path +import shutil +from markdown_blocks import markdown_to_html_node + +def toPublicFolder(source, destination): + if os.path.exists(destination): + shutil.rmtree(destination) + os.mkdir(destination) + for file in os.listdir(source): + if os.path.isfile(os.path.join(source, file)): + shutil.copy(os.path.join(source, file), destination) + else: + toPublicFolder(os.path.join(source, file), os.path.join(destination, file)) + +def extract_title(markdown): + for line in markdown.split("\n"): + if line.startswith("# "): + return line.replace("#", "").strip() + raise Exception() + +def generate_page(from_path, template_path, dest_path): + print(f" * {from_path} {template_path} -> {dest_path}") + from_file = open(from_path, "r") + markdown_content = from_file.read() + from_file.close() + + template_file = open(template_path, "r") + template = template_file.read() + template_file.close() + + node = markdown_to_html_node(markdown_content) + html = node.to_html() + + title = extract_title(markdown_content) + template = template.replace("{{ Title }}", title) + template = template.replace("{{ Content }}", html) + + dest_dir_path = os.path.dirname(dest_path) + if dest_dir_path != "": + os.makedirs(dest_dir_path, exist_ok=True) + to_file = open(dest_path, "w") + to_file.write(template) + +def generate_pages_recursive(dir_path_content, template_path, dest_dir_path): + for filename in os.listdir(dir_path_content): + from_path = os.path.join(dir_path_content, filename) + dest_path = os.path.join(dest_dir_path, filename) + if os.path.isfile(from_path): + dest_path = Path(dest_path).with_suffix(".html") + generate_page(from_path, template_path, dest_path) + else: + generate_pages_recursive(from_path, template_path, dest_path) \ No newline at end of file diff --git a/static/images/rivendell.png b/static/images/rivendell.png new file mode 100644 index 0000000..1fa5201 Binary files /dev/null and b/static/images/rivendell.png differ diff --git a/static/index.css b/static/index.css new file mode 100644 index 0000000..d16e5e5 --- /dev/null +++ b/static/index.css @@ -0,0 +1,93 @@ +body { + background-color: #0d1117; + color: #c9d1d9; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + line-height: 1.5; + margin: 0; + padding: 20px; + max-width: 800px; + margin-left: auto; + margin-right: auto; +} + +b { + font-weight: 900; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: #58a6ff; + margin-top: 24px; + margin-bottom: 16px; +} + +h1 { + font-size: 2em; +} + +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.17em; +} + +h4, +h5, +h6 { + font-size: 1em; +} + +a { + color: #58a6ff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +ul, +ol { + padding-left: 20px; +} + +code { + background-color: #242424; + border-radius: 6px; + color: #d2a8ff; + padding: 0.2em 0.4em; + font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; +} + +pre code { + padding: 0; +} + +pre { + background-color: #242424; + border-radius: 6px; + padding: 0.2em 0.4em; +} + +blockquote { + background-color: #242424; + border-left: 4px solid #30363d; + padding-left: 2em; + margin-left: 0; + padding-top: 0.5em; + padding-bottom: 0.5em; + padding-right: 0.5em; + color: #8b949e; +} + +img { + max-width: 100%; + height: auto; + border-radius: 6px; +} \ No newline at end of file diff --git a/template.html b/template.html new file mode 100644 index 0000000..518d075 --- /dev/null +++ b/template.html @@ -0,0 +1,17 @@ + + + + + + + {{ Title }} + + + + +
+ {{ Content }} +
+ + + \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..377b1e8 --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +python3 -m unittest discover -s src \ No newline at end of file