commit 9656f3b061a4ec43449ccf2abe18580317899267
Author: Ryan Fox
Date: Mon Jul 13 02:35:49 2020 +0000
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..67f550c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+ftp_url.txt
+music/*
+dist/
+scripts/__pycache__/
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..19f6d17
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,22 @@
+The code & design of this website are under the MIT license.
+Do not assume anything about the contents of the website itself.
+
+Copyright (c) 2020 Ryan Fox
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8b65639
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+PAGES_IN = $(wildcard pages/*)
+POSTS_IN = $(wildcard posts/*.md)
+MUSIC_IN = $(wildcard music/*.mp3)
+
+PAGES_OUT = $(addprefix dist/,$(notdir $(PAGES_IN:.md=.html)))
+POSTS_OUT = $(addprefix dist/blog/,$(notdir $(POSTS_IN:.md=.html)))
+MUSIC_OUT = $(addprefix dist/music/,$(notdir $(MUSIC_IN)))
+
+POST_LISTS = dist/blog/index.html dist/blog/feed.xml
+MUSIC_LISTS = dist/music/index.html dist/music/feed.xml
+
+all: $(PAGES_OUT) $(POSTS_OUT) $(MUSIC_OUT) $(POST_LISTS) $(MUSIC_LISTS)
+ cp -urv static/. dist
+
+dist/%.html: pages/%.md | dist
+ scripts/template_md.py $< $@
+
+dist/%.html: pages/%.html | dist
+ cp $< $@
+
+dist/blog/%.html: posts/%.md | dist/blog
+ scripts/template_md.py $< $@
+
+dist/music/%.mp3: music/%.mp3 | dist/music
+ cp $< $@
+
+$(POST_LISTS): $(POSTS_OUT)
+ scripts/gen_blog.py posts $@
+
+$(MUSIC_LISTS): music.json | $(OUTPUT_MUSIC)
+ scripts/gen_music.py $< $@
+
+dist/blog:
+ mkdir -p dist/blog
+
+dist/music:
+ mkdir -p dist/music
+
+dist:
+ mkdir dist
+
+.PHONY: clean
+
+upload: dist
+ lftp -c "set ftp:list-options -a;\
+ set ssl:verify-certificate false;\
+ open '$(shell cat ftp_url.txt)';\
+ lcd dist;\
+ cd public_html;\
+ mirror --reverse"
+
+clean:
+ rm -rf dist
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..9d752d8
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,6 @@
+flewkey.com
+
+This repository contains flewkey.com, my personal website.
+Jekyll was too complicated for me, so I cobbled together some Python scripts.
+
+Send questions, issues, and patches to flewkey@2a03.party.
diff --git a/music.json b/music.json
new file mode 100644
index 0000000..6a9eb96
--- /dev/null
+++ b/music.json
@@ -0,0 +1,210 @@
+[
+ {
+ "id": "calm_down",
+ "name": "Calm Down",
+ "description": "This is a bit of a mess, but I like some of the ideas here.",
+ "timestamp": 1588820400
+ },
+ {
+ "id": "larry_the_cow",
+ "name": "Larry the Cow",
+ "description": "I haven't posted any music since May, so here's something I never uploaded.",
+ "timestamp": 1588462200
+ },
+ {
+ "id": "microwave",
+ "name": "Microwave",
+ "description": "This isn't a coherent song, my goal was to have fun.",
+ "timestamp": 1583470800
+ },
+ {
+ "id": "scrap_5",
+ "name": "Scrap #5",
+ "description": "I tried Ardour for the first time. It's fun.",
+ "group": "Scraps",
+ "timestamp": 1582677000
+ },
+ {
+ "id": "scrap_4",
+ "name": "Scrap #4",
+ "description": "I tried publishing this without Windows this time.",
+ "group": "Scraps",
+ "timestamp": 1582509600
+ },
+ {
+ "id": "scrap_3",
+ "name": "Scrap #3",
+ "description": "I thought this sounded neat.",
+ "group": "Scraps",
+ "timestamp": 1580418000
+ },
+ {
+ "id": "scrap_2",
+ "name": "Scrap #2",
+ "description": "Another scrapped idea.",
+ "group": "Scraps",
+ "timestamp": 1578792600
+ },
+ {
+ "id": "baby_steps",
+ "name": "Baby Steps",
+ "description": "I tried using Ableton to edit a song, and I think it went well.",
+ "timestamp": 1575594000
+ },
+ {
+ "id": "cold_spoon",
+ "name": "Cold Spoon",
+ "description": "I pulled this out of my unfinished folder. Very loopy.",
+ "timestamp": 1574973000
+ },
+ {
+ "id": "feeling_chaotic",
+ "name": "Feeling Chaotic",
+ "description": "I felt like using FamiTracker again, so I did this quickly.",
+ "timestamp": 1573686000
+ },
+ {
+ "id": "scrap_1",
+ "name": "Scrap #1",
+ "description": "I'm going to post \"scraps\" now. These happen when I don't want to finish something I make.",
+ "group": "Scraps",
+ "timestamp": 1565319600
+ },
+ {
+ "id": "seriously_what_the_hell_is_this",
+ "name": "Seriously What The Hell Is This",
+ "description": "Still figuring stuff out. Why do all Ableton instruments sounds weird? Yeah screw this, I'm not making any more.",
+ "timestamp": 1562025600
+ },
+ {
+ "id": "learning_new_software",
+ "name": "Learning New Software",
+ "description": "My first attempt making music outside of a tracker. I spent nearly an hour fighting MuseScore and I have no clue what happened.",
+ "timestamp": 1561939200
+ },
+ {
+ "id": "screw_this",
+ "name": "Screw This!",
+ "description": "FamiSpam failed due to my complete laziness. This is the last song I will upload to the series. Afterwards, I'm probably going to learn proper composition and stop making tracker music for a while.",
+ "group": "FamiSpam",
+ "timestamp": 1560384000
+ },
+ {
+ "id": "puzzle_house",
+ "name": "Puzzle House",
+ "description": "Happy birthday DevEd!",
+ "group": "FamiSpam",
+ "timestamp": 1558396800
+ },
+ {
+ "id": "shaking_off_rust",
+ "name": "Shaking Off Rust",
+ "description": "I haven't made music for a while. Most of this is terrible, but that won't stop me from publishing it.",
+ "group": "FamiSpam",
+ "timestamp": 1557360000
+ },
+ {
+ "id": "back_into_it",
+ "name": "Back Into It",
+ "description": "Sorry this one sounds awkward, I feel rusty with this. I quit after 12 days last time, so I'll try to beat my previous streak.",
+ "group": "FamiSpam",
+ "timestamp": 1555804800
+ },
+ {
+ "id": "it_gets_better",
+ "name": "It Gets Better",
+ "description": "I was frustrated with this at first.",
+ "group": "FamiSpam",
+ "timestamp": 1554595200
+ },
+ {
+ "id": "boring_and_loopy",
+ "name": "Boring and Loopy",
+ "description": "I tried to make a loopy song, and it turned out terribly. I'll try to do better next time.",
+ "group": "FamiSpam",
+ "timestamp": 1554422400
+ },
+ {
+ "id": "racing_or_something",
+ "name": "Racing or Something",
+ "description": "This is one of my favourite ones so far.",
+ "group": "FamiSpam",
+ "timestamp": 1554249600
+ },
+ {
+ "id": "the_confusing_one",
+ "name": "The Confusing One",
+ "description": "Made in confusion.",
+ "group": "FamiSpam",
+ "timestamp": 1554076800
+ },
+ {
+ "id": "the_first_half_sucks",
+ "name": "The First Half Sucks",
+ "description": "I have no clue how to use arps properly.",
+ "group": "FamiSpam",
+ "timestamp": 1553904000
+ },
+ {
+ "id": "music_box",
+ "name": "Music Box",
+ "description": "I'm not happy with my music, so I've resolved to make 30 seconds of music a day. My goal is to write 20 minutes of music in 2 months.",
+ "group": "FamiSpam",
+ "timestamp": 1553731200
+ },
+ {
+ "id": "laziness",
+ "name": "Laziness.mp3",
+ "description": "I made this for an OHB on Battle of the Bits.",
+ "group": "FamiSpam",
+ "timestamp": 1551207600
+ },
+ {
+ "id": "filthy_foe",
+ "name": "Filthy Foe.nsf",
+ "description": "I made this for an OHB on Battle of the Bits.",
+ "timestamp": 1551193200
+ },
+ {
+ "id": "ohc03815_famitracker_pack_pack_0cc",
+ "name": "OHC03815 famitracker pack pack.0cc",
+ "description": "I made this for an OHB on Battle of the Bits.",
+ "timestamp": 1551171600
+ },
+ {
+ "id": "unsanitary_snow",
+ "name": "Unsanitary Snow",
+ "description": "A track I made for Winter Chip XIV on Battle of the Bits. Hopefully I'll have the time to try participating in OHBs this year.",
+ "timestamp": 1550016000
+ },
+ {
+ "id": "we_give_up",
+ "name": "We Give Up",
+ "description": "Some short FamiTracker songs.",
+ "timestamp": 1547942400
+ },
+ {
+ "id": "running_away",
+ "name": "Running Away",
+ "description": "Some short FamiTracker songs.",
+ "timestamp": 1547942400
+ },
+ {
+ "id": "begin_quest",
+ "name": "Begin Quest",
+ "description": "Some short FamiTracker songs.",
+ "timestamp": 1547942400
+ },
+ {
+ "id": "spooky_sunset",
+ "name": "Spooky Sunset",
+ "description": "Messing around in FamiTracker again.",
+ "timestamp": 1546905600
+ },
+ {
+ "id": "spaceship",
+ "name": "Spaceship",
+ "description": "An old-ish track I wanted to consider finished.",
+ "timestamp": 1532822400
+ }
+]
diff --git a/pages/about.md b/pages/about.md
new file mode 100644
index 0000000..a6ccc84
--- /dev/null
+++ b/pages/about.md
@@ -0,0 +1,50 @@
+template: page
+title: Info
+license: CC-BY
+
+# About Me
+
+Ahoy, my name is Ryan Fox. I am a Canadian guy who likes programming and music.
+
+I have not done anything to earn online credibility, and you are most likely
+here because I sent you a link. Despite this, you are spending time reading my
+website, and I am very grateful for that.
+
+---
+
+## Encryption
+
+These are my PGP and SSH keys. If you would like to encrypt messages, or allow
+me to access your server, these are the ones to use. I am somewhat confident
+that they will not get compromised.
+
+* PGP: [D910C0CAC5BF5739861A4ABD94F56ADFD848851E]({root}pgp.txt)
+* SSH: [SHA256:IggQMESZIgwIP4/zfYI8ZA1xnMd9g9huOUtgfaKjmCo]({root}ssh.txt)
+
+---
+
+## Profiles
+
+Here is a list of my main profiles, the ones worth listing.
+
+* GitHub: [@flewkey](https://github.com/flewkey)
+* Twitter: [@flewkey](https://twitter.com/flewkey)
+* YouTube: [@flewkey](https://www.youtube.com/channel/UCRUfdaM8EiXu7SCfGl8FUpQ)
+* PeerTube: [@flewkey_channel@toobnix.org](https://toobnix.org/video-channels/flewkey_channel/)
+* Mastodon: [@flewkey@layer8.space](https://layer8.space/@flewkey)
+* Newgrounds: [@flewkey](https://flewkey.newgrounds.com/)
+* Soundcloud: [@flewkey](https://soundcloud.com/flewkey)
+* SDF GIT Society [@flewkey](https://git.sdf.org/flewkey)
+
+---
+
+## Contact
+
+If you would like to contact me privately, please send me an e-mail if possible.
+Even a long, drawn out e-mail will take less time out of my day than the average
+text exchange. Thanks!
+
+I can also be found at [##dystopia]({root}dystopia{ext}) on Freenode. While I
+encourage people to use the IRC channel, there is also a
+[Discord server]({root}dystopia{ext}) linked to the chatroom for those who find
+IRC inconvenient for some reason.
diff --git a/pages/dystopia.md b/pages/dystopia.md
new file mode 100644
index 0000000..1ce82e9
--- /dev/null
+++ b/pages/dystopia.md
@@ -0,0 +1,30 @@
+template: page
+title: Dystopia
+license: CC-BY
+
+# Dystopia
+
+Dystopia is a personal Discord server and IRC channel which I run for myself,
+and various other people I know. It is also one of the easiest places to contact
+me.
+
+All messages on the IRC channel will be mirrored to Discord and logged.
+
+---
+
+## Links
+
+* \#\#dystopia on Freenode
+([webchat](https://webchat.freenode.net/?channels=##dystopia))
+* on Discord
+
+---
+
+## Rules
+
+* Use common sense.
+* Do not annoy others.
+* Absolutely no flame wars.
+* Conduct yourself as a guest.
+* Never post slurs, I won't allow it.
+* Avoid posting personal information.
diff --git a/pages/index.md b/pages/index.md
new file mode 100644
index 0000000..09c34c7
--- /dev/null
+++ b/pages/index.md
@@ -0,0 +1,19 @@
+template: page
+title: Homepage
+license: CC-BY
+
+# Homepage
+
+Ahoy, my name is Ryan Fox. I am a Canadian guy who likes programming and music.
+
+Have you come to [read about me]({root}about{ext})?
+
+If not, feel free to [read some blog posts]({root}blog/) or
+[listen to my music]({root}music/).
+
+---
+
+## Projects
+
+* [flewkey-overlay](https://git.sdf.org/flewkey/flewkey-overlay) - My personal Gentoo overlay.
+* [minecraft-tweaks-2a03](https://git.sdf.org/flewkey/minecraft-tweaks-2a03) - A basic Minecraft mod.
diff --git a/posts/2020-06-30-blog.md b/posts/2020-06-30-blog.md
new file mode 100644
index 0000000..008ec61
--- /dev/null
+++ b/posts/2020-06-30-blog.md
@@ -0,0 +1,21 @@
+template: post
+title: Website update
+author: flewkey
+timestamp: 1593548958
+license: CC-BY
+
+I like blogs. Blogs are one of the only suitable platforms for people to
+properly present ideas. There are no character limits, the author can embed
+whatever they want, and — most importantly — they can disable the
+comments section. This caught my attention, so I decided to start my own blog.
+Unfortunately, blogging on my plain HTML website was a pain. It needed an
+update.
+
+Multiple people had recommended Jekyll to me, and I had seen many others use it
+for their websites. Making a simple blog with it seemed simple, but extending
+it to handle a custom page for my music was a pain. It didn't help that I am far
+too lazy to learn about Ruby.
+
+The solution was ridiculously simple: a Makefile and some Python scripts. I had
+to fiddle with it for a couple of hours, but I am quite happy with the result.
+There should be less typos in the RSS feeds now!
diff --git a/posts/2020-07-12-nds-constraint.md b/posts/2020-07-12-nds-constraint.md
new file mode 100644
index 0000000..b3eb272
--- /dev/null
+++ b/posts/2020-07-12-nds-constraint.md
@@ -0,0 +1,97 @@
+template: post
+title: nds-constrain't
+author: flewkey
+timestamp: 1594525504
+license: CC-BY
+
+Once upon a time, shutterbug2000 discovered nds-constrain't: a bug in Nintendo's
+NTR SSL library that allowed it's connections to be easily intercepted. This
+made it possible to connect to alternative online services without ROM patching.
+
+The bug itself is simple: the NDS SSL library does not care whether or not a
+certificate is authorized to act as a certificate authority. This means that
+— with any valid certificate — we can sign whatever we want, even a
+certificate under a false hostname.
+
+A guide to using this bug for fun and profit is now available on the
+[official page](https://github.com/KaeruTeam/nds-constraint), which is much
+better written than mine. However, you are free to keep reading this one.
+
+---
+
+### Getting the Wii client certificate
+
+As explained in the [official page](https://github.com/KaeruTeam/nds-constraint)
+for nds-constrain't, the Wii client certificate is signed by Nintendo and
+considered valid. Therefore, we can use it's key to sign whatever we want. You
+_could_ grab it from a Wii, but it is much easier to download it from
+[Larsenv's page](https://larsenv.github.io/NintendoCerts/index.html). You will
+want to use the link labelled "Wii NWC Prod 1", by the way.
+
+### Converting it to a useable format
+
+The file is a PKCS12, and we can't do anything useful with it until we extract
+the certificate and the private key. Thankfully, this is pretty simple.
+
+ openssl pkcs12 -in WII_NWC_1_CERT.p12 -passin pass:alpine -passout pass:alpine -out keys.txt
+
+That command will export the X.509 certificate and private key from the archive,
+and store the output in keys.txt. They can then be copied into their appropriate
+files, which I will name NWC.crt and NWC.key.
+
+### Signing your certificate
+
+Instructions for this are listed on the official GitHub page, but I have copied
+them for reference. If I remember correctly, the DS can only handle the SHA-1
+and MD5 hash formats, so pay attention to the `-sha1` flag.
+
+ openssl genrsa -out server.key 1024
+ openssl req -new -key server.key -out server.csr
+ openssl x509 -req -in server.csr -CA NWC.crt -CAkey NWC.key -CAcreateserial -out server.crt -days 3650 -sha1
+
+Your webserver probably wants the certificate chain as well, so let's generate
+that as well.
+
+ cat server.crt NWC.crt > server-chain.crt
+
+We are ready to rock and roll!
+
+### Using your phony certificate
+
+Once the SSL certificate is installed, you may run into issues connecting with
+your DS. This because your NDS only knows how to use SSLv3, with the SHA-1 or
+MD5 cipher sets. Enabling SSLv3 and SHA-1 isn't always possible with webservers,
+so I recommend using NGINX as a reverse-proxy. To enable DS compatibility for
+NGINX, add the following lines to your NGINX configuration.
+
+ ssl_protocols SSLv3;
+ ssl_ciphers ECDHE-RSA-AES128-SHA;
+
+Most services on Nintendo consoles make liberal use of headers. Because of this,
+some extra options need to be enabled.
+
+ underscores_in_headers on;
+ proxy_pass_request_headers on;
+
+Because we have enabled insecure SSL settings on NGINX, you probably don't want
+to use it for any mission-critical web applications. If you continue having
+issues with NDS connectivity, please contact me.
+
+### Having fun
+
+The possibilities are infinite. Want to run services through a debugging proxy?
+Implement WFC protocols? Make a Flipnote Studio server? All of this is possible
+without ROM patches!
+
+---
+
+### Update
+
+After receiving some e-mails, I have learned that the NDS-supported ciphers have
+been disabled in OpenSSL versions past 1.0.2g, unless configured with
+"enable-weak-ssl-ciphers". This means that you may have to re-build NGINX (or
+mod_ssl for Apache) to get it working.
+
+If you have the means, I suggest taking Wireshark captures to find the cause of
+any SSL issues. Enabling debug logging in NGINX can also help you pinpoint
+handshake errors. If all else fails, you can find my e-mail on the about page.
diff --git a/scripts/gen_blog.py b/scripts/gen_blog.py
new file mode 100755
index 0000000..1da213c
--- /dev/null
+++ b/scripts/gen_blog.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import os
+import sys
+import markdown
+from template import template
+
+def gen_list(files, ext):
+ md = markdown.Markdown(extensions = ["meta", "extra"])
+ items = ""
+ for post in files:
+ with open("posts/"+post, "r", encoding="utf-8") as file_in:
+ html = md.convert(file_in.read())
+ md.Meta["template"] = "post_preview"
+ items += template(html, "blog/"+post[:-3]+ext, ext, md.Meta)
+ return items
+
+def main():
+ meta = {"template": "post_list"}
+ ext = "."+sys.argv[2].split(".")[-1]
+ files = sorted(os.listdir(sys.argv[1]), reverse=True)
+ output = template(gen_list(files, ext), sys.argv[2], ext, meta)
+ with open(sys.argv[2], "w+", encoding="utf-8") as file_out:
+ file_out.write(output)
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/gen_music.py b/scripts/gen_music.py
new file mode 100755
index 0000000..ae61b93
--- /dev/null
+++ b/scripts/gen_music.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import os
+import sys
+import json
+from template import template
+
+def gen_list(music, ext):
+ items = ""
+ for track in music:
+ track["template"] = "track_preview"
+ track["file_size"] = str(os.path.getsize("music/"+track["id"]+".mp3"))
+ if "group" in track:
+ track["group_text"] = "Part of "+track["group"]+" "
+ items += template("", sys.argv[2], ext, track)
+ return items
+
+def main():
+ meta = {"template": "music_list"}
+ ext = "."+sys.argv[2].split(".")[-1]
+ with open(sys.argv[1], "r", encoding="utf-8") as file_in:
+ music = json.loads(file_in.read())
+ output = template(gen_list(music, ext), sys.argv[2], ext, meta)
+ with open(sys.argv[2], "w+", encoding="utf-8") as file_out:
+ file_out.write(output)
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/template.py b/scripts/template.py
new file mode 100644
index 0000000..cfcad89
--- /dev/null
+++ b/scripts/template.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import markdown
+from datetime import datetime
+from email import utils
+
+def get_path(path, ext_in, ext_out):
+ path = path[len("pages"):]
+ if path[-len("index"+ext_in):] == "index"+ext_in:
+ return path[:-len("index"+ext_in)]
+ return path[:-len(ext_in)] + ext_out
+
+def get_dir(path):
+ return "/".join(path.split("/")[1:-1])+"/"
+
+def get_root(path):
+ root = ""
+ for i in range(len(path.split("/"))-2):
+ root += "../"
+ return root
+
+licenses = {
+ "CC-BY": ["cc-by", "CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/"]
+}
+
+def get_badge(license, root):
+ if license not in licenses:
+ return ""
+ data = licenses[license]
+ return ""
+
+def read_template(template, meta_in):
+ if template[:10] != "meta: yes\n":
+ return [template, {"meta": "no"}]
+ data = template[:-1].split("\n\n") # Strip trailing newline
+ md = markdown.Markdown(extensions = ["meta"])
+ md.convert(data[0])
+ content = "\n\n".join(data[1:])
+ content = content.replace("{", "{{").replace("}", "}}")
+ meta = meta_in.copy()
+ meta.update(md.Meta)
+ return [content, MetaDict(meta)]
+
+defaults = {
+ "base": "https://flewkey.com/",
+ "title": "Untitled",
+ "author": "flewkey",
+ "ext": ".html",
+ "head": ""
+}
+
+class MetaDict(dict):
+ def __missing__(self, key):
+ return defaults[key] if key in defaults else ""
+ def __getitem__(self, key):
+ value = dict.__getitem__(self, key)
+ if isinstance(value, list):
+ return "\n".join(value)
+ else:
+ return value
+
+def template(content, path, ext, meta):
+ global base
+ meta = MetaDict(meta)
+ if meta["template"] == None:
+ return content.format_map(meta)
+ with open("templates/"+meta["template"]+ext, "r", encoding="utf-8") as file:
+ t_content = file.read()
+ t_info = read_template(t_content, meta)
+ if t_info[1]["meta"] != "no" and "template" in t_info[1]:
+ t_content = template(t_info[0], path, ext, t_info[1])
+ if "root" not in meta:
+ meta["root"] = get_root(path)
+ if "home" not in meta:
+ meta["home"] = "." if meta["root"] == "" else meta["root"]
+ if "full_path" not in meta:
+ meta["full_path"] = get_path(path, ext, meta["ext"])
+ if "full_dir" not in meta:
+ meta["full_dir"] = get_dir(path)
+ if "badge" not in meta and ext == ".html":
+ meta["badge"] = get_badge(meta["license"], meta["root"])
+ if "date" not in meta and "timestamp" in meta:
+ meta["date"] = datetime.fromtimestamp(int(meta["timestamp"])).isoformat().split("T")[0]
+ if "pub_date" not in meta and "timestamp" in meta:
+ meta["pub_date"] = utils.format_datetime(datetime.fromtimestamp(int(meta["timestamp"])))
+ meta["head"] += "\n"
+ meta["content"] = content.format_map(meta)
+ if "description" not in meta and ext == ".html":
+ meta["description"] = meta["content"].split("
+{content}
diff --git a/templates/post_list.html b/templates/post_list.html
new file mode 100644
index 0000000..8772f7a
--- /dev/null
+++ b/templates/post_list.html
@@ -0,0 +1,11 @@
+meta: yes
+title: Blog
+description: View a post, then view more posts.
+head:
+template: page
+
+
Blog
+
The chances of seeing a new blog post are the same as winning the lottery while getting struck by lightning twice on a leap day. If you still come across a post, I promise to make it disappointing.