Reorganized
4
MANIFEST.in
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
graft flaskr/static
|
||||||
|
graft flaskr/templates
|
||||||
|
graft flaskr/blog
|
||||||
|
global-exclude *.pyc
|
159
flaskr/__init__.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
from flask import Flask, render_template, send_from_directory, abort, request, make_response
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
import markdown2
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import ast
|
||||||
|
|
||||||
|
markdown_extras = ["fenced-code-blocks", "footnotes", "strike", "tables", "metadata"]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"feet": [
|
||||||
|
"🐝",
|
||||||
|
"Best viewed using Internet Explorer 6 or earlier",
|
||||||
|
"The HORSE is a noble animal",
|
||||||
|
"🦀",
|
||||||
|
"<code>segmentation fault (core dumped)</code>",
|
||||||
|
"Bees land on thyme",
|
||||||
|
"☃",
|
||||||
|
"<code># cat /dev/urandom > /dev/sda</code>",
|
||||||
|
"<code>:(){ :|: & };:</code>",
|
||||||
|
"Formal complaints will recieve responses within 5-7 business days",
|
||||||
|
"<code>++++[->++++<]>+[->++++++>+++++++>++<<<]>.>--------..+++++.<-.>--.>--.<++.<.>++++.----.</code>",
|
||||||
|
"Copywrong © 3034. All rights unreserved.",
|
||||||
|
"[<span style='text-decoration: underline;'>citation needed</span>]",
|
||||||
|
"Best viewed with eyes",
|
||||||
|
"Your browser does not support 7D graphics. Please update for the best user experience.",
|
||||||
|
"Press SPACE to jump",
|
||||||
|
"🐀",
|
||||||
|
"If problems persist, please return to the nearest Blockbuster Video® establishment",
|
||||||
|
"Oversalt to taste",
|
||||||
|
"curl -s -L http://bit.ly/10hA8iC | bash",
|
||||||
|
"Submit footer text via carrier pigeon to <code>[REDACTED]</code>",
|
||||||
|
"GEORGE is inevitable."
|
||||||
|
],
|
||||||
|
"next_theme": {
|
||||||
|
"system": "dark",
|
||||||
|
"dark": "light",
|
||||||
|
"light": "contrast",
|
||||||
|
"contrast": "system"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def index_projects(app):
|
||||||
|
projects = []
|
||||||
|
proj_dir = os.path.join(app.root_path, app.template_folder, "projects")
|
||||||
|
for root, dirs, files in os.walk(proj_dir):
|
||||||
|
for file in files:
|
||||||
|
try:
|
||||||
|
with open(os.path.join(root, file)) as f:
|
||||||
|
metaline = f.read().splitlines()[0]
|
||||||
|
if metaline.startswith("{% set meta="):
|
||||||
|
meta = metaline[12:-2].strip()
|
||||||
|
meta = ast.literal_eval(meta)
|
||||||
|
meta["path"] = os.path.relpath(root, start=proj_dir)
|
||||||
|
meta["file"] = file
|
||||||
|
projects.append(meta)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return sorted(projects, key=lambda p: p.get("title"))
|
||||||
|
|
||||||
|
def index_blog(app):
|
||||||
|
blogposts = []
|
||||||
|
blog_dir = os.path.join(app.root_path, "blog")
|
||||||
|
for file in os.listdir(blog_dir):
|
||||||
|
with open(os.path.join(blog_dir, file)) as f:
|
||||||
|
contents = f.read()
|
||||||
|
html = markdown2.markdown(contents, extras=markdown_extras)
|
||||||
|
meta = html.metadata
|
||||||
|
meta["file"] = file
|
||||||
|
date_parts = file.removesuffix(".md").split("-")
|
||||||
|
date = datetime.date(int(date_parts[0]), int(date_parts[1]), int(date_parts[2]))
|
||||||
|
meta["timestamp"] = datetime.datetime.fromisoformat(meta["timestamp"])
|
||||||
|
meta["date"] = date
|
||||||
|
meta["n"] = int(date_parts[3])
|
||||||
|
meta["url"] = f"/blog/{date.isoformat().replace('-','/')}/{meta['n']}"
|
||||||
|
blogposts.append(meta)
|
||||||
|
return sorted(blogposts, key=lambda post:[post["date"], post["n"]], reverse=True)
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.wsgi_app = ProxyFix(
|
||||||
|
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
|
||||||
|
)
|
||||||
|
data["blogposts"] = index_blog(app)
|
||||||
|
data["projects"] = index_projects(app)
|
||||||
|
|
||||||
|
@app.route("/favicon.ico")
|
||||||
|
def favicon():
|
||||||
|
path = os.path.join(app.root_path, "static")
|
||||||
|
return send_from_directory(path, "favicon.ico", mimetype="image/vnd.microsoft.icon")
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def four_oh_four(e):
|
||||||
|
theme = request.cookies.get("theme") or "dark"
|
||||||
|
return render_template("404.html", data=data, theme=theme)
|
||||||
|
|
||||||
|
def load_page(url):
|
||||||
|
if url.endswith(".html"):
|
||||||
|
path = os.path.join(app.root_path, app.template_folder, url)
|
||||||
|
if os.path.exists(path):
|
||||||
|
theme = request.cookies.get("theme") or "dark"
|
||||||
|
return render_template(url, data=data, theme=theme)
|
||||||
|
else:
|
||||||
|
return abort(404)
|
||||||
|
else:
|
||||||
|
return send_from_directory("templates", url)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
@app.route("/index.html")
|
||||||
|
def home():
|
||||||
|
return load_page("index.html")
|
||||||
|
|
||||||
|
@app.route("/projects/")
|
||||||
|
@app.route("/projects/index.html")
|
||||||
|
def projects_index():
|
||||||
|
return load_page("projects.html")
|
||||||
|
|
||||||
|
@app.route("/projects/<page>/")
|
||||||
|
@app.route("/projects/<page>/<file>")
|
||||||
|
def project(page, file="index.html"):
|
||||||
|
return load_page(f"projects/{page}/{file}")
|
||||||
|
|
||||||
|
@app.route("/blog/")
|
||||||
|
def blog_list():
|
||||||
|
theme = request.cookies.get("theme") or "dark"
|
||||||
|
return render_template("blog.html", data=data, theme=theme)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/blog/<int:y>/<int:m>/<int:d>/")
|
||||||
|
@app.route("/blog/<int:y>/<int:m>/<int:d>/<int:n>")
|
||||||
|
def blog_page(y, m, d, n=0):
|
||||||
|
date = datetime.date(y, m, d)
|
||||||
|
print(date.isoformat())
|
||||||
|
path = os.path.join(app.root_path, "blog", f"{date.isoformat()}-{n}.md")
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path) as f:
|
||||||
|
contents = f.read()
|
||||||
|
content = markdown2.markdown(contents, extras=markdown_extras)
|
||||||
|
meta = content.metadata
|
||||||
|
theme = request.cookies.get("theme") or "dark"
|
||||||
|
return render_template("_blog.html", data=data, theme=theme, content=content, date=date, meta=meta)
|
||||||
|
else:
|
||||||
|
return abort(404)
|
||||||
|
|
||||||
|
@app.route("/blog/rss.xml")
|
||||||
|
def blog_rss():
|
||||||
|
xml = render_template("rss.xml", data=data)
|
||||||
|
response = make_response(xml)
|
||||||
|
response.headers["Content-Type"] = "application/rss+xml; charset=utf-8"
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route("/blog/atom.xml")
|
||||||
|
def blog_atom():
|
||||||
|
xml = render_template("atom.xml", data=data)
|
||||||
|
response = make_response(xml)
|
||||||
|
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
||||||
|
return response
|
||||||
|
|
||||||
|
return app
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 277 KiB After Width: | Height: | Size: 277 KiB |
Before Width: | Height: | Size: 806 B After Width: | Height: | Size: 806 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
@ -65,7 +65,7 @@
|
||||||
:root[theme="contrast"] {
|
:root[theme="contrast"] {
|
||||||
--bg: white;
|
--bg: white;
|
||||||
--bg-intense: white;
|
--bg-intense: white;
|
||||||
--bg-faded: #c0c0c0;
|
--bg-faded: #d0d0d0;
|
||||||
--fg: black;
|
--fg: black;
|
||||||
--fg-faded: #444444;
|
--fg-faded: #444444;
|
||||||
--accent-1: blue;
|
--accent-1: blue;
|
|
@ -3,7 +3,6 @@
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<style>
|
||||||
.left > span { display: block; }
|
.left > span { display: block; }
|
||||||
.colored { color: var(--accent-2); }
|
|
||||||
#errors { color: #ff4444; }
|
#errors { color: #ff4444; }
|
||||||
.short { width: 70px; }
|
.short { width: 70px; }
|
||||||
main {
|
main {
|
||||||
|
@ -14,9 +13,6 @@ main {
|
||||||
.left, .right {
|
.left, .right {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
.category {
|
|
||||||
color: var(--accent-1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"
|
||||||
integrity="sha512-zhHQR0/H5SEBL3Wn6yYSaTTZej12z0hVZKOv3TwCUXT1z5qeqGcXJLLrbERYRScEDDpYIJhPC1fk31gqR783iQ=="
|
integrity="sha512-zhHQR0/H5SEBL3Wn6yYSaTTZej12z0hVZKOv3TwCUXT1z5qeqGcXJLLrbERYRScEDDpYIJhPC1fk31gqR783iQ=="
|
13
setup.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="flaskr",
|
||||||
|
version="1.0.0",
|
||||||
|
packages=find_packages(),
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
install_requires=[
|
||||||
|
"flask",
|
||||||
|
"markdown2",
|
||||||
|
],
|
||||||
|
)
|
150
src/main.py
|
@ -1,150 +0,0 @@
|
||||||
from flask import Flask, render_template, send_from_directory, abort, request, make_response
|
|
||||||
import markdown2
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import ast
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"feet": [
|
|
||||||
"🐝",
|
|
||||||
"Best viewed using Internet Explorer 6 or earlier",
|
|
||||||
"The HORSE is a noble animal",
|
|
||||||
"🦀",
|
|
||||||
"<code>segmentation fault (core dumped)</code>",
|
|
||||||
"Bees land on thyme",
|
|
||||||
"☃",
|
|
||||||
"<code># cat /dev/urandom > /dev/sda</code>",
|
|
||||||
"<code>:(){ :|: & };:</code>",
|
|
||||||
"Formal complaints will recieve responses within 5-7 business days",
|
|
||||||
"<code>++++[->++++<]>+[->++++++>+++++++>++<<<]>.>--------..+++++.<-.>--.>--.<++.<.>++++.----.</code>",
|
|
||||||
"Copywrong © 3034. All rights unreserved.",
|
|
||||||
"[<span style='text-decoration: underline;'>citation needed</span>]",
|
|
||||||
"Best viewed with eyes",
|
|
||||||
"Your browser does not support 7D graphics. Please update for the best user experience.",
|
|
||||||
"Press SPACE to jump",
|
|
||||||
"🐀",
|
|
||||||
"If problems persist, please return to the nearest Blockbuster Video® establishment",
|
|
||||||
"Oversalt to taste",
|
|
||||||
"curl -s -L http://bit.ly/10hA8iC | bash",
|
|
||||||
"Submit footer text via carrier pigeon to <code>[REDACTED]</code>",
|
|
||||||
"GEORGE is inevitable."
|
|
||||||
],
|
|
||||||
"next_theme": {
|
|
||||||
"system": "dark",
|
|
||||||
"dark": "light",
|
|
||||||
"light": "contrast",
|
|
||||||
"contrast": "system"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markdown_extras = ["fenced-code-blocks", "footnotes", "strike", "tables", "metadata"]
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
projects = []
|
|
||||||
proj_dir = os.path.join(app.root_path, app.template_folder, "projects")
|
|
||||||
for root, dirs, files in os.walk(proj_dir):
|
|
||||||
for file in files:
|
|
||||||
try:
|
|
||||||
with open(os.path.join(root, file)) as f:
|
|
||||||
metaline = f.read().splitlines()[0]
|
|
||||||
if metaline.startswith("{% set meta="):
|
|
||||||
meta = metaline[12:-2].strip()
|
|
||||||
meta = ast.literal_eval(meta)
|
|
||||||
meta["path"] = os.path.relpath(root, start=proj_dir)
|
|
||||||
meta["file"] = file
|
|
||||||
projects.append(meta)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
data["projects"] = sorted(projects, key=lambda p: [p.get("star") != True, p.get("title")])
|
|
||||||
|
|
||||||
blogposts = []
|
|
||||||
blog_dir = os.path.join(app.root_path, "blog")
|
|
||||||
for file in os.listdir(blog_dir):
|
|
||||||
with open(os.path.join(blog_dir, file)) as f:
|
|
||||||
contents = f.read()
|
|
||||||
html = markdown2.markdown(contents, extras=markdown_extras)
|
|
||||||
meta = html.metadata
|
|
||||||
meta["file"] = file
|
|
||||||
date_parts = file.removesuffix(".md").split("-")
|
|
||||||
date = datetime.date(int(date_parts[0]), int(date_parts[1]), int(date_parts[2]))
|
|
||||||
meta["timestamp"] = datetime.datetime.fromisoformat(meta["timestamp"])
|
|
||||||
meta["date"] = date
|
|
||||||
meta["n"] = int(date_parts[3])
|
|
||||||
meta["url"] = f"/blog/{date.isoformat().replace('-','/')}/{meta['n']}"
|
|
||||||
blogposts.append(meta)
|
|
||||||
data["blogposts"] = sorted(blogposts, key=lambda post:[post["date"], post["n"]], reverse=True)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/favicon.ico")
|
|
||||||
def favicon():
|
|
||||||
path = os.path.join(app.root_path, "static")
|
|
||||||
return send_from_directory(path, "favicon.ico", mimetype="image/vnd.microsoft.icon")
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def four_oh_four(e):
|
|
||||||
theme = request.cookies.get("theme") or "dark"
|
|
||||||
return render_template("404.html", data=data, theme=theme)
|
|
||||||
|
|
||||||
def load_page(url):
|
|
||||||
if url.endswith(".html"):
|
|
||||||
path = os.path.join(app.root_path, app.template_folder, url)
|
|
||||||
if os.path.exists(path):
|
|
||||||
theme = request.cookies.get("theme") or "dark"
|
|
||||||
return render_template(url, data=data, theme=theme)
|
|
||||||
else:
|
|
||||||
return abort(404)
|
|
||||||
else:
|
|
||||||
return send_from_directory("templates", url)
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
@app.route("/index.html")
|
|
||||||
def home():
|
|
||||||
return load_page("index.html")
|
|
||||||
|
|
||||||
@app.route("/projects/")
|
|
||||||
@app.route("/projects/index.html")
|
|
||||||
def projects_index():
|
|
||||||
return load_page("projects.html")
|
|
||||||
|
|
||||||
@app.route("/projects/<page>/")
|
|
||||||
@app.route("/projects/<page>/<file>")
|
|
||||||
def project(page, file="index.html"):
|
|
||||||
return load_page(f"projects/{page}/{file}")
|
|
||||||
|
|
||||||
@app.route("/blog/")
|
|
||||||
def blog_list():
|
|
||||||
theme = request.cookies.get("theme") or "dark"
|
|
||||||
return render_template("blog.html", data=data, theme=theme)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/blog/<int:y>/<int:m>/<int:d>/")
|
|
||||||
@app.route("/blog/<int:y>/<int:m>/<int:d>/<int:n>")
|
|
||||||
def blog_page(y, m, d, n=0):
|
|
||||||
date = datetime.date(y, m, d)
|
|
||||||
print(date.isoformat())
|
|
||||||
path = os.path.join(app.root_path, "blog", f"{date.isoformat()}-{n}.md")
|
|
||||||
if os.path.exists(path):
|
|
||||||
with open(path) as f:
|
|
||||||
contents = f.read()
|
|
||||||
content = markdown2.markdown(contents, extras=markdown_extras)
|
|
||||||
meta = content.metadata
|
|
||||||
theme = request.cookies.get("theme") or "dark"
|
|
||||||
return render_template("_blog.html", data=data, theme=theme, content=content, date=date, meta=meta)
|
|
||||||
else:
|
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
@app.route("/blog/rss.xml")
|
|
||||||
def blog_rss():
|
|
||||||
xml = render_template("rss.xml", data=data)
|
|
||||||
response = make_response(xml)
|
|
||||||
response.headers["Content-Type"] = "application/rss+xml; charset=utf-8"
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.route("/blog/atom.xml")
|
|
||||||
def blog_atom():
|
|
||||||
xml = render_template("atom.xml", data=data)
|
|
||||||
response = make_response(xml)
|
|
||||||
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
|
||||||
return response
|
|
2
test.sh
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
export FLASK_APP=src/main
|
export FLASK_APP=flaskr
|
||||||
export FLASK_ENV=development
|
export FLASK_ENV=development
|
||||||
flask run
|
flask run
|