Working on config and Pydantic types

This commit is contained in:
Jim Shepich III 2026-01-30 10:02:09 -05:00
parent 006aeab2c1
commit d56569668d
12 changed files with 295 additions and 63 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ shepich resume.pdf
**/.env **/.env
tmp tmp
build build
dist dist
**/__pycache__

Binary file not shown.

View File

@ -0,0 +1,30 @@
author: Jim Shepich III
site_defaults:
base_url: http://localhost:8000
web_root: ./dist
templates:
partials: ./templates/partials
components: ./templates/components
pages: ./templates/pages
sites:
main:
build_cache: ./build
assets:
- ./assets
articles:
- ./pages/*.md
resume:
git_repo: ssh://gitea/jim/resume.git
build_cache: ./build/resume
assets:
- '{build_cache}/shepich_resume.pdf'
dogma_jimfinium:
base_url: http://localhost:8080/dogma-jimfinium
git_repo: ssh://gitea/jim/dogma-jimfinium.git
build_cache: ./build/dogma-jimfinium
web_root: ./dist/dogma-jimfinium
assets:
- '{build_cache}/assets'
articles:
- '{build_cache}/*.md'

71
main.py
View File

@ -2,7 +2,9 @@ import os
import subprocess import subprocess
import markdown import markdown
import yaml import yaml
from datetime import datetime import pydantic
from typing import Optional
from datetime import datetime, date
def filepath_or_string(s: str) -> str: def filepath_or_string(s: str) -> str:
'''Loads the contents of a string if it is a filepath, otherwise returns the string.''' '''Loads the contents of a string if it is a filepath, otherwise returns the string.'''
@ -13,15 +15,37 @@ def filepath_or_string(s: str) -> str:
return s return s
def load_markdown(md: str) -> tuple[dict, str]: with open('config.yaml', 'r') as config_file:
'''Loads a Markdown file into a (metadata: dict, content: str) pair.''' config = yaml.safe_load(config_file.read())
class SiteConfig(pydantic.BaseModel):
base_url: Optional[str] = config['site_defaults'].get('base_url')
git_repo: Optional[str] = config['site_defaults'].get('git_repo')
build_cache: Optional[str] = config['site_defaults'].get('build_cache')
assets: Optional[list] = config['site_defaults'].get('assets')
web_root: Optional[str] = config['site_defaults'].get('web_root')
articles: Optional[list] = config['site_defaults'].get('articles')
class ArticleMetadata(pydantic.BaseModel):
title: str
author: Optional[str] = config.get('author')
date: date
lastmod: Optional[date]
published: bool
tags: list
thumbnail: Optional[str] = None
def load_markdown(md: str) -> tuple[ArticleMetadata|None, str]:
'''Loads a Markdown file into a (metadata: ArticleMetadata, content: str) pair.'''
# Load the file contents if a filepath is specified, and strip document delimiters ('---'). # Load the file contents if a filepath is specified, and strip document delimiters ('---').
md = filepath_or_string(md).strip().strip('---').strip() md = filepath_or_string(md).strip().strip('---').strip()
# If there is no `---` delimiter, then the article has no metadata. # If there is no `---` delimiter, then the article has no metadata.
if '---' not in md.strip('---'): if '---' not in md.strip('---'):
return {}, md return None, md
# Split the metadata from the contents. # Split the metadata from the contents.
[raw_metadata, raw_article] = md.split('---') [raw_metadata, raw_article] = md.split('---')
@ -32,7 +56,7 @@ def load_markdown(md: str) -> tuple[dict, str]:
# Convert the contents to a HTML string. # Convert the contents to a HTML string.
content = markdown.markdown(raw_article) content = markdown.markdown(raw_article)
return metadata, content return ArticleMetadata(**metadata), content
def format_html_template(template: str, **kwargs) -> str: def format_html_template(template: str, **kwargs) -> str:
@ -107,5 +131,42 @@ def format_blog_tags(tags: list[str], template = 'templates/components/blog_tag.
format_html_template(template, tag_name = t) for t in tags format_html_template(template, tag_name = t) for t in tags
] ]
def build_blog_archive(
index: dict[str, tuple[str, str]],
page_template = 'templates/pages/default.html',
li_template = 'templates/components/blog_archive_li.html',
**kwargs
) -> str:
'''Converts an index, formatted as filestem: (metadata, contents) dict,
into an HTML page containing the list of articles, sorted from newest to oldest.
Note: partials must be expanded into the kwargs, as they are needed to generate
the overall page.
'''
# Add each article as a list item to an unordered list.
archive_html_content = '<ul>'
for article, (metadata, contents) in sorted(index.items(), key = lambda item: item[1][0]['date'])[::-1]:
# Generate HTML for the article (including metadata tags).
archive_html_content += format_html_template(
li_template,
article_filestem = article,
blog_tags = ' '.join(format_blog_tags(metadata.tags)),
metadata = metadata
)
archive_html_content +='</ul>'
# Interpolate the article into the overall page template.
archive_html_page = format_html_template(
page_template,
content = archive_html_content,
**kwargs
)
return archive_html_page
if __name__ == '__main__': if __name__ == '__main__':
pass pass

View File

@ -1,8 +0,0 @@
<article>
<h1>Welcome!</h1>
<hr />
<p>Welcome to my little corner of the Internet! My name is Jim <span title="'&#643;&#603;p&#618;k">Shepich</span> (@epicshepich). I like to introduce myself as a jack-of-all-trades with a Master's in data science. My main interests include STEM, martial arts (especially jūjutsu), sci-fi and fantasy, tabletop and video gaming, cartoons and comics, and DIY, but I also love to branch out and learn new things! My dream is to become "The Most Interesting Man in the World."</p>
<p>This website started as a way for me to exercise and showcase my skills with vanilla HTML, JavaScript, CSS, and PHP. Now, it's just a matter of filling the site with content! My vision for this website is for it to serve as a gift to my future reincarnated self (if that's what happens) &mdash; a way to pass on the little tips and tricks, bits of wisdom, resources, and treasures I've accumulated over the course of my life (think <i>new game plus</i>). And if some other kindred spirits find something here that helps them on their journey, that's a bonus! </p>
<article>

10
pages/home.md Normal file
View File

@ -0,0 +1,10 @@
---
title: Welcome
date: 2026-01-30
published: true
tags: []
---
Welcome to my little corner of the Internet! My name is Jim <span title="'&#643;&#603;p&#618;k">Shepich</span> (@epicshepich). I like to introduce myself as a jack-of-all-trades with a Master's in data science. My main interests include STEM, martial arts (especially jūjutsu), sci-fi and fantasy, tabletop and video gaming, cartoons and comics, and DIY, but I also love to branch out and learn new things! My dream is to become "The Most Interesting Man in the World."
This website started as a way for me to exercise and showcase my skills with vanilla HTML, JavaScript, CSS, and PHP. Now, it's just a matter of filling the site with content! My vision for this website is for it to serve as a gift to my future reincarnated self (if that's what happens) &mdash; a way to pass on the little tips and tricks, bits of wisdom, resources, and treasures I've accumulated over the course of my life (think _new game plus_). And if some other kindred spirits find something here that helps them on their journey, that's a bonus!

View File

@ -1,3 +1,4 @@
ipykernel ipykernel
markdown markdown
pyyaml pyyaml
rfeed

View File

@ -9,11 +9,11 @@
<body> <body>
<main> <main>
<article> <article>
<h1 class="headline">{metadata__title}</h1> <h1 class="headline">{metadata.title}</h1>
<p class="byline"> <p class="byline">
<address class="author">By <a rel="author" href="mailto:admin@jimlab.io">Jim Shepich III</a></address> <address class="author">By <a rel="author" href="mailto:admin@jimlab.io">Jim Shepich III</a></address>
<br/>First published: <time pubdate datetime="{metadata__date}">{metadata__date}</time> <br/>First published: <time pubdate datetime="{metadata.date}">{metadata.date}</time>
<br/>Last modified: <time pubdate datetime="{metadata__lastmod}">{metadata__lastmod}</time> <br/>Last modified: <time pubdate datetime="{metadata.lastmod}">{metadata.lastmod}</time>
</p> </p>
{content} {content}
</article> </article>

View File

@ -1 +1 @@
<li>{metadata__date} - <a href='{article_filestem}.html'>{metadata__title}</a> {blog_tags}</li> <li>{metadata.date} - <a href='{article_filestem}.html'>{metadata.title}</a> {blog_tags}</li>

View File

@ -1,11 +1,11 @@
<article> <article>
<h1 class="headline">{metadata__title}</h1> <h1 class="headline">{metadata.title}</h1>
<p class="byline"> <p class="byline">
<address class="author">By <a rel="author" href="mailto:admin@jimlab.io">Jim Shepich III</a></address> <address class="author">By <a rel="author" href="mailto:admin@jimlab.io">Jim Shepich III</a></address>
<br/>First published: <time pubdate datetime="{metadata__date}">{metadata__date}</time> <br/>First published: <time pubdate datetime="{metadata.date}">{metadata.date}</time>
<br/>Last modified: <time pubdate datetime="{metadata__lastmod}">{metadata__lastmod}</time> <br/>Last modified: <time pubdate datetime="{metadata.lastmod}">{metadata.lastmod}</time>
</p> </p>
{content} {content}
<hr /> <br /><hr /><br />
<p>{blog_tags}</p> <p>{blog_tags}</p>
</article> </article>

View File

@ -0,0 +1,14 @@
<html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
{partials__default_css}
{partials__header}
{partials__nav}
</head>
<body>
<main>
{content}
</main>
{partials__footer}
</body>

View File

@ -27,15 +27,20 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": null,
"id": "207d2510", "id": "207d2510",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"import os\n", "import os\n",
"import shutil\n",
"import markdown\n", "import markdown\n",
"import yaml\n", "import yaml\n",
"import subprocess\n", "import subprocess\n",
"import rfeed\n",
"import pydantic\n",
"from typing import Optional, Union, Literal, BinaryIO, Any\n",
"\n",
"\n", "\n",
"\n", "\n",
"from datetime import datetime\n", "from datetime import datetime\n",
@ -44,24 +49,68 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": null,
"id": "d2361c42", "id": "4b17a3ed",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"data": {
"text/plain": [
"'dist/assets'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"metadata, content = load_markdown('tmp/dogma-jimfinium/blowouts.md')\n", "PARTIALS = load_partials()\n",
"content = content.replace('src=\"assets', 'src=\"../tmp/dogma-jimfinium/assets')" "shutil.rmtree('dist/assets', ignore_errors=True)\n",
"shutil.copytree('assets','dist/assets')"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31, "execution_count": null,
"id": "d2361c42",
"metadata": {},
"outputs": [
{
"ename": "ValidationError",
"evalue": "2 validation errors for ArticleMetadata\ndate\n Input should be a valid string [type=string_type, input_value=datetime.date(2026, 1, 30), input_type=date]\n For further information visit https://errors.pydantic.dev/2.12/v/string_type\nlastmod\n Field required [type=missing, input_value={'title': 'Welcome', 'dat...shed': True, 'tags': []}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.12/v/missing",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mValidationError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m metadata, content = \u001b[43mload_markdown\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mpages/home.md\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# content = content.replace('src=\"assets', 'src=\"../tmp/dogma-jimfinium/assets')\u001b[39;00m\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/projects/shepich.com/main.py:59\u001b[39m, in \u001b[36mload_markdown\u001b[39m\u001b[34m(md)\u001b[39m\n\u001b[32m 56\u001b[39m \u001b[38;5;66;03m# Convert the contents to a HTML string.\u001b[39;00m\n\u001b[32m 57\u001b[39m content = markdown.markdown(raw_article)\n\u001b[32m---> \u001b[39m\u001b[32m59\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mArticleMetadata\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mmetadata\u001b[49m\u001b[43m)\u001b[49m, content\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/projects/shepich.com/.venv/lib/python3.12/site-packages/pydantic/main.py:250\u001b[39m, in \u001b[36mBaseModel.__init__\u001b[39m\u001b[34m(self, **data)\u001b[39m\n\u001b[32m 248\u001b[39m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[32m 249\u001b[39m __tracebackhide__ = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m250\u001b[39m validated_self = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[43m.\u001b[49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 251\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m validated_self:\n\u001b[32m 252\u001b[39m warnings.warn(\n\u001b[32m 253\u001b[39m \u001b[33m'\u001b[39m\u001b[33mA custom validator is returning a value other than `self`.\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m'\u001b[39m\n\u001b[32m 254\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mReturning anything other than `self` from a top level model validator isn\u001b[39m\u001b[33m'\u001b[39m\u001b[33mt supported when validating via `__init__`.\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 255\u001b[39m \u001b[33m'\u001b[39m\u001b[33mSee the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.\u001b[39m\u001b[33m'\u001b[39m,\n\u001b[32m 256\u001b[39m stacklevel=\u001b[32m2\u001b[39m,\n\u001b[32m 257\u001b[39m )\n",
"\u001b[31mValidationError\u001b[39m: 2 validation errors for ArticleMetadata\ndate\n Input should be a valid string [type=string_type, input_value=datetime.date(2026, 1, 30), input_type=date]\n For further information visit https://errors.pydantic.dev/2.12/v/string_type\nlastmod\n Field required [type=missing, input_value={'title': 'Welcome', 'dat...shed': True, 'tags': []}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.12/v/missing"
]
}
],
"source": [
"metadata, content = load_markdown('pages/home.md')\n",
"# content = content.replace('src=\"assets', 'src=\"../tmp/dogma-jimfinium/assets')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0bb15524",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed7b3b2f", "id": "ed7b3b2f",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"PARTIALS = load_partials()\n", "html = format_html_template('templates/pages/default.html', content = content, **{'metadata.'+k:v for k,v in metadata.items()}, **PARTIALS)\n",
"html = format_html_template('templates/blog_post.html', content = content, **{'metadata__'+k:v for k,v in metadata.items()}, **PARTIALS)\n",
"with open('dist/home.html', 'w') as f:\n", "with open('dist/home.html', 'w') as f:\n",
" f.write(html)" " f.write(html)"
] ]
@ -102,13 +151,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 58, "execution_count": null,
"id": "944a5efd", "id": "944a5efd",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"run(f'rm -rf dist/dogma-jimfinium && mkdir -p dist/dogma-jimfinium')\n", "shutil.rmtree('dist/dogma-jimfinium', ignore_errors=True)\n",
"run(f'cp -r build/dogma-jimfinium/assets dist/dogma-jimfinium')\n", "os.makedirs('dist/dogma-jimfinium', exist_ok=True)\n",
"shutil.copytree('build/dogma-jimfinium/assets', 'dist/dogma-jimfinium/assets')\n",
"\n", "\n",
"index = {}\n", "index = {}\n",
"\n", "\n",
@ -129,26 +179,122 @@
" 'templates/components/blog_article.html',\n", " 'templates/components/blog_article.html',\n",
" content = content,\n", " content = content,\n",
" blog_tags = ' '.join(format_blog_tags(metadata['tags'])),\n", " blog_tags = ' '.join(format_blog_tags(metadata['tags'])),\n",
" **{'metadata__'+k:v for k,v in metadata.items()}\n", " **{'metadata.'+k:v for k,v in metadata.items()}\n",
" )\n", " )\n",
" html = format_html_template('templates/simple.html', content = article_html, **PARTIALS)\n", " html = format_html_template('templates/pages/default.html', content = article_html, **PARTIALS)\n",
" \n", " \n",
" # Write the HTML file to /dist/dogma-jimfinium.\n", " # Write the HTML file to /dist/dogma-jimfinium.\n",
" with open(f'dist/dogma-jimfinium/{article_filestem}.html', 'w') as f:\n", " with open(f'dist/dogma-jimfinium/{article_filestem}.html', 'w') as f:\n",
" f.write(html)\n", " f.write(html)\n",
"\n", "\n",
"\n", "\n",
"index_html = build_blog_archive(index, metadata__title = 'Dogma Jimfinium | Index')\n", "index_html = build_blog_archive(index, metadata.title = 'Dogma Jimfinium | Index', **PARTIALS)\n",
"# Write the HTML file to /dist/dogma-jimfinium.\n", "# Write the HTML file to /dist/dogma-jimfinium.\n",
"with open(f'dist/dogma-jimfinium/index.html', 'w') as f:\n", "with open(f'dist/dogma-jimfinium/index.html', 'w') as f:\n",
" f.write(index_html)" " f.write(index_html)"
] ]
}, },
{
"cell_type": "code",
"execution_count": null,
"id": "132a32ec",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"datetime.date(2024, 7, 12)"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"metadata['date']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d48110fc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"datetime.datetime(2024, 7, 12, 0, 0)"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"datetime(metadata['date'].year, metadata['date'].month, metadata['date'].day)"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "e32458c7", "id": "e32458c7",
"metadata": {}, "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<rss version=\"2.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"><channel><title>Dogma Jimfinium</title><link>http://localhost:8000/dogma-jimfinium/rss</link><description>Dogma Jimfinium</description><language>en-US</language><lastBuildDate>Thu, 29 Jan 2026 16:29:57 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>Superlock</title><link>http://localhost:8000/dogma-jimfinium/superlock</link><author>Jim Shepich III</author><pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">superlock</guid></item><item><title>Sustainable Living</title><link>http://localhost:8000/dogma-jimfinium/sustainable-living</link><author>Jim Shepich III</author><pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">sustainable-living</guid></item><item><title>Stocking Up</title><link>http://localhost:8000/dogma-jimfinium/stocking-up</link><author>Jim Shepich III</author><pubDate>Wed, 19 Nov 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">stocking-up</guid></item><item><title>Set Up the Toys</title><link>http://localhost:8000/dogma-jimfinium/set-up-the-toys</link><author>Jim Shepich III</author><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">set-up-the-toys</guid></item><item><title>Do What You Love</title><link>http://localhost:8000/dogma-jimfinium/do-what-you-love</link><author>Jim Shepich III</author><pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">do-what-you-love</guid></item><item><title>Self-Care is not Selfish</title><link>http://localhost:8000/dogma-jimfinium/self-care-is-not-selfish</link><author>Jim Shepich III</author><pubDate>Sun, 18 May 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">self-care-is-not-selfish</guid></item><item><title>Blowouts</title><link>http://localhost:8000/dogma-jimfinium/blowouts</link><author>Jim Shepich III</author><pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">blowouts</guid></item><item><title>Vitamins &amp; Supplements</title><link>http://localhost:8000/dogma-jimfinium/vitamins</link><author>Jim Shepich III</author><pubDate>Sun, 18 May 2025 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">vitamins</guid></item><item><title>Gear for New Parents</title><link>http://localhost:8000/dogma-jimfinium/gear-for-new-parents</link><author>Jim Shepich III</author><pubDate>Fri, 12 Jul 2024 00:00:00 GMT</pubDate><guid isPermaLink=\"true\">gear-for-new-parents</guid></item></channel></rss>\n"
]
}
],
"source": [
"rss_feed = rfeed.Feed(\n",
" title = \"Dogma Jimfinium\",\n",
" link = \"http://localhost:8000/dogma-jimfinium/rss\",\n",
" description = \"Dogma Jimfinium\",\n",
" language = \"en-US\",\n",
" lastBuildDate = datetime.now(),\n",
" items = [\n",
" rfeed.Item(\n",
" title = metadata['title'],\n",
" link = f\"http://localhost:8000/dogma-jimfinium/{filestem}\", \n",
" description = metadata.get('description'),\n",
" author = metadata.get('author', 'Jim Shepich III'),\n",
" guid = rfeed.Guid(filestem),\n",
" pubDate = datetime(metadata['date'].year, metadata['date'].month, metadata['date'].day)\n",
" )\n",
" for filestem, (metadata, _) in index.items()\n",
" ]\n",
")\n",
"\n",
"print(rss_feed.rss())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c160693",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "57ef8185",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "b068c448",
"metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": []
}, },
@ -159,31 +305,8 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"def build_blog_archive(index: dict[str, tuple[str, str]], template = 'templates/simple.html', **kwargs) -> str:\n", "def build_site(site_config: SiteConfig):\n",
" '''Converts an index, formatted as filestem: (metadata, contents) dict,\n", " "
" into an HTML page containing the list of articles, sorted from newest to oldest.'''\n",
"\n",
" # Add each article in the format `YYYY-MM-DD - Title`\n",
" index_html_content = '<ul>'\n",
" for article, (metadata, contents) in sorted(index.items(), key = lambda item: item[1][0]['date'])[::-1]:\n",
" blog_tags = ' '.join(format_blog_tags(metadata['tags']))\n",
" index_html_content += format_html_template(\n",
" 'templates/components/blog_archive_li.html',\n",
" article_filestem = article,\n",
" blog_tags = blog_tags,\n",
" **{'metadata__'+k:v for k,v in metadata.items()}\n",
" \n",
" )\n",
" index_html_content +='</ul>'\n",
"\n",
" index_html_page = format_html_template(\n",
" template, \n",
" content = index_html_content, \n",
" **kwargs,\n",
" **PARTIALS\n",
" )\n",
" \n",
" return index_html_page"
] ]
} }
], ],