First commit

This commit is contained in:
franky212
2024-11-29 19:11:11 -07:00
commit 585eb3f749
19 changed files with 1101 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__/
public/

32
content/index.md Normal file
View File

@@ -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!")
}
```

68
content/majesty/index.md Normal file
View File

@@ -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.

2
main.sh Executable file
View File

@@ -0,0 +1,2 @@
python3 src/main.py
cd public && python3 -m http.server 8888

53
src/htmlnode.py Normal file
View File

@@ -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}</{self.tag}>"
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}</{self.tag}>"
def __repr__(self):
return f"ParentNode({self.tag}, children: {self.children}, {self.props})"

100
src/inline_markdown.py Normal file
View File

@@ -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"(?<!!)\[([^\[\]]*)\]\(([^\(\)]*)\)"
matches = re.findall(pattern, text)
return matches

26
src/main.py Normal file
View File

@@ -0,0 +1,26 @@
import os
import shutil
from utilities import toPublicFolder, generate_pages_recursive
dir_path_static = "./static"
dir_path_public = "./public"
dir_path_content = "./content"
template_path = "./template.html"
def main():
print("Deleting public directory...")
if os.path.exists(dir_path_public):
shutil.rmtree(dir_path_public)
print("Copying static files to public directory...")
toPublicFolder("./static", "./public")
print("Generating content...")
generate_pages_recursive(
os.path.join(dir_path_content),
template_path,
os.path.join(dir_path_public)
)
main()

150
src/markdown_blocks.py Normal file
View File

@@ -0,0 +1,150 @@
from htmlnode import ParentNode
from inline_markdown import text_to_textnodes
from textnode import text_node_to_html_node
block_type_paragraph = "paragraph"
block_type_heading = "heading"
block_type_code = "code"
block_type_quote = "quote"
block_type_olist = "ordered_list"
block_type_ulist = "unordered_list"
def markdown_to_blocks(markdown):
blocks = markdown.split("\n\n")
filtered_blocks = []
for block in blocks:
if block == "":
continue
block = block.strip()
filtered_blocks.append(block)
return filtered_blocks
def block_to_block_type(block):
lines = block.split("\n")
if block.startswith(("# ", "## ", "### ", "#### ", "##### ", "###### ")):
return block_type_heading
if len(lines) > 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)

106
src/test_htmlnode.py Normal file
View File

@@ -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(), "<p>Hello, world!</p>")
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(), "<div><span>child</span></div>")
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(),
"<div><span><b>grandchild</b></span></div>",
)
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(),
"<p><b>Bold text</b>Normal text<i>italic text</i>Normal text</p>",
)
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(),
"<h2><b>Bold text</b>Normal text<i>italic text</i>Normal text</h2>",
)
if __name__ == "__main__":
unittest.main()

196
src/test_inline_markdown.py Normal file
View File

@@ -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()

View File

@@ -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()

59
src/test_textnode.py Normal file
View File

@@ -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()

26
src/test_utilities.py Normal file
View File

@@ -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()

43
src/textnode.py Normal file
View File

@@ -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}")

53
src/utilities.py Normal file
View File

@@ -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)

BIN
static/images/rivendell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

93
static/index.css Normal file
View File

@@ -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;
}

17
template.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> {{ Title }} </title>
<link href="/index.css" rel="stylesheet">
</head>
<body>
<article>
{{ Content }}
</article>
</body>
</html>

1
test.sh Executable file
View File

@@ -0,0 +1 @@
python3 -m unittest discover -s src