From 8b38b3e86db0fd67d7bdf47cde9d9ddabca0d7e1 Mon Sep 17 00:00:00 2001
From: Jim Shepich III
Date: Wed, 28 Jan 2026 16:56:03 -0500
Subject: [PATCH] Forgot to stage changes
---
.gitignore | 7 +-
__pycache__/main.cpython-312.pyc | Bin 0 -> 3854 bytes
agenda.md | 21 ----
info.php | 5 -
main.py | 79 +++++++++++++++
requirements.txt | 3 +
templates/default.html | 23 +++++
templates/partials/blog_article.html | 0
templates/partials/default_css.html | 4 +
templates/partials/footer.html | 3 +
templates/partials/header.html | 3 +
templates/partials/nav.html | 4 +
testbench.ipynb | 142 +++++++++++++++++++++++++++
13 files changed, 267 insertions(+), 27 deletions(-)
create mode 100644 __pycache__/main.cpython-312.pyc
delete mode 100644 agenda.md
delete mode 100644 info.php
create mode 100644 main.py
create mode 100644 requirements.txt
create mode 100644 templates/default.html
create mode 100644 templates/partials/blog_article.html
create mode 100644 templates/partials/default_css.html
create mode 100644 templates/partials/footer.html
create mode 100644 templates/partials/header.html
create mode 100644 templates/partials/nav.html
create mode 100644 testbench.ipynb
diff --git a/.gitignore b/.gitignore
index 78ecffe..87a67e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
-shepich resume.pdf
\ No newline at end of file
+shepich resume.pdf
+**/.venv
+**/.env
+tmp
+build
+dist
\ No newline at end of file
diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..590ae1839a37bb4c5921b88943450ad4bf9aedd1
GIT binary patch
literal 3854
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
literal 0
HcmV?d00001
diff --git a/agenda.md b/agenda.md
deleted file mode 100644
index c543ab3..0000000
--- a/agenda.md
+++ /dev/null
@@ -1,21 +0,0 @@
-## Pages
-- Home
-- About
-- CV
- - Everything in as much detail as possible
-- Projects
- - E&E
- - M|Chroma
- - SI-Formatter
-- Socials (maybe better as a footer)
- - Instagram
- - Facebook
- - LinkedIn
- - GitHub
- - MAL
-- Bookmarks (?)
-
-## Reference Material
-- https://www.tomscott.com/
-- http://vihart.com/
-- https://www.singingbanana.com/
diff --git a/info.php b/info.php
deleted file mode 100644
index 47b3cb9..0000000
--- a/info.php
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..cb46608
--- /dev/null
+++ b/main.py
@@ -0,0 +1,79 @@
+import os
+import subprocess
+import markdown
+import yaml
+from datetime import datetime
+
+def filepath_or_string(s: str) -> str:
+ '''Loads the contents of a string if it is a filepath, otherwise returns the string.'''
+ if os.path.isfile(s):
+ with open(s, 'r') as f:
+ return f.read()
+ else:
+ return s
+
+
+def load_markdown(md: str) -> tuple[dict, str]:
+ '''Loads a Markdown file into a (metadata: dict, content: str) pair.'''
+
+ # Load the file contents if a filepath is specified, and strip document delimiters ('---').
+ md = filepath_or_string(md).strip().strip('---').strip()
+
+ # If there is no `---` delimiter, then the article has no metadata.
+ if '---' not in md.strip('---'):
+ return {}, md
+
+ # Split the metadata from the contents.
+ [raw_metadata, raw_article] = md.split('---')
+
+ # Use YAML to parse the metadata.
+ metadata = yaml.safe_load(raw_metadata)
+
+ # Convert the contents to a HTML string.
+ content = markdown.markdown(raw_article)
+
+ return metadata, content
+
+
+def format_html_template(template: str, **kwargs) -> str:
+ '''Interpolates variables specified as keyword arguments
+ into the given HTML template.'''
+
+ # Load the template if a filepath is given.
+ template = filepath_or_string(template)
+
+ # Interpolate the kwargs into the HTML template.
+ html = template.format(**kwargs)
+
+ # Return the formatted HTML.
+ return html
+
+
+REPOS = [
+ 'ssh://gitea/jim/resume.git',
+ 'ssh://gitea/jim/dogma-jimfinium.git'
+]
+run = lambda cmd: subprocess.run(cmd.split(' '), stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+def update_git_repos(repos: list) -> None:
+ '''Pulls updates to repos in the build directory, or clones them if they don't exist.'''
+ for repo in repos:
+ local_path = 'build/'+repo.split('/')[-1].strip('.git')
+ print(local_path)
+ if os.path.exists(f'{local_path}/.git'):
+ run(f'git -C {local_path} pull origin')
+ else:
+ run(f'git clone {repo} {local_path}')
+
+
+def load_partials() -> dict:
+ """Loads partial templates from the templates/partials directory."""
+ partials = {}
+ for filename in os.listdir('templates/partials'):
+ with open(f'templates/partials/{filename}') as partial_file:
+ partial_template = partial_file.read()
+
+ partials[f'partials__{os.path.splitext(filename)[0]}'] = format_html_template(
+ partial_template,
+ current_year = datetime.now().year
+ )
+ return partials
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..735541d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+ipykernel
+markdown
+pyyaml
\ No newline at end of file
diff --git a/templates/default.html b/templates/default.html
new file mode 100644
index 0000000..3cc4d24
--- /dev/null
+++ b/templates/default.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ {partials__default_css}
+ {partials__header}
+ {partials__nav}
+
+
+
+
+
+ {metadata__title}
+
+
By Jim Shepich III
+
First published:
+
Last modified:
+
+ {content}
+
+ {partials__footer}
+
\ No newline at end of file
diff --git a/templates/partials/blog_article.html b/templates/partials/blog_article.html
new file mode 100644
index 0000000..e69de29
diff --git a/templates/partials/default_css.html b/templates/partials/default_css.html
new file mode 100644
index 0000000..0bc0f4c
--- /dev/null
+++ b/templates/partials/default_css.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/templates/partials/footer.html b/templates/partials/footer.html
new file mode 100644
index 0000000..4764231
--- /dev/null
+++ b/templates/partials/footer.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/templates/partials/header.html b/templates/partials/header.html
new file mode 100644
index 0000000..45c5c32
--- /dev/null
+++ b/templates/partials/header.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/templates/partials/nav.html b/templates/partials/nav.html
new file mode 100644
index 0000000..9e34918
--- /dev/null
+++ b/templates/partials/nav.html
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/testbench.ipynb b/testbench.ipynb
new file mode 100644
index 0000000..9269cad
--- /dev/null
+++ b/testbench.ipynb
@@ -0,0 +1,142 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "dda60de8",
+ "metadata": {},
+ "source": [
+ "## Roadmap\n",
+ "\n",
+ "- [x] Load markdown\n",
+ "- [] Determine static website structure\n",
+ " - Where to put assets for subsites like dogma jimfinium\n",
+ " - How to otherwise organize pages\n",
+ "- [] Resolve markdown links\n",
+ "- [] Consider separating article templates and overall page templates\n",
+ "- [] RSS feed\n",
+ "\n",
+ "\n",
+ "WEBROOT\n",
+ "- assets\n",
+ "- main pages\n",
+ "- resume\n",
+ "- dogma-jimfinium/\n",
+ " - assets/\n",
+ " - pages"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "207d2510",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import markdown\n",
+ "import yaml\n",
+ "import subprocess\n",
+ "\n",
+ "\n",
+ "from datetime import datetime\n",
+ "from main import *"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "d2361c42",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "metadata, content = load_markdown('tmp/dogma-jimfinium/blowouts.md')\n",
+ "content = content.replace('src=\"assets', 'src=\"../tmp/dogma-jimfinium/assets')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "ed7b3b2f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "PARTIALS = load_partials()\n",
+ "html = format_html_template('templates/default.html', content = content, **{'metadata__'+k:v for k,v in metadata.items()}, **PARTIALS)\n",
+ "with open('dist/home.html', 'w') as f:\n",
+ " f.write(html)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b8c87620",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "57383c24",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "2025-12-01\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "CompletedProcess(args=['cp', 'build/resume/2025-12-01/shepich_resume.pdf', 'dist/shepich_resume.pdf'], returncode=0, stdout=b'', stderr=b'')"
+ ]
+ },
+ "execution_count": 170,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def get_latest_resume():\n",
+ " max_date = '0000-00-00'\n",
+ " for resume_folder in os.listdir('build/resume'):\n",
+ " try:\n",
+ " datetime.strptime(resume_folder,'%Y-%m-%d')\n",
+ " except Exception:\n",
+ " continue\n",
+ " \n",
+ " if resume_folder > max_date:\n",
+ " max_date = resume_folder\n",
+ " \n",
+ "\n",
+ " print(max_date)\n",
+ " # print(max_date.strftime('%Y-%m-%d'))\n",
+ " \n",
+ " run(f'cp build/resume/{max_date}/shepich_resume.pdf dist/shepich_resume.pdf')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}