commit 41c80533cd2572a99fc8408c754ed450147bd95d Author: Noah Petherbridge Date: Tue Jul 3 19:41:33 2018 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ccfa09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +*.pyc +/public diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a79b96 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: build + +@PHONY: build +build: + ./build.py + +@PHONY: watch +watch: + ./watch.sh + +@PHONY: clean +clean: + rm -rf public diff --git a/README.md b/README.md new file mode 100644 index 0000000..f83a32c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +all: build + +build: diff --git a/build.py b/build.py new file mode 100755 index 0000000..bba5fab --- /dev/null +++ b/build.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 + +import codecs +import logging +import os +from shutil import copyfile + +import jinja2 + +logging.basicConfig() +log = logging.getLogger("build") +log.setLevel(logging.INFO) + +class App(object): + # Global template variables. + def vars(self): + return { + "site": { + "name": "noah.is", + }, + } + + def __init__(self): + # Jinja environment. + self.env = jinja2.Environment( + loader=jinja2.FileSystemLoader("./templates"), + ) + + # Collect all the templates and all the static files. + self.templates = self.crawl("./templates") # TODO: remove list() wrapper + self.static = self.crawl("./static") + + # Copy all of the static files into the public root. + if not os.path.isdir("./public"): + self.mkdir("./public") + for static in self.static: + public = static.replace("./static/", "./public/") + self.mkdir(public.rsplit("/", 1)[0]) # ensure the path will exist + if self.should_copy_static(static, public): + log.info("COPY STATIC FILE %s -> %s", static, public) + copyfile(static, public) + + # And render all of the templates. + for template in self.templates: + if template.startswith("./templates/layout"): # don't create these + continue + public = self.template_to_public(template) + self.mkdir(public.rsplit("/", 1)[0]) # ensure the path will exist + log.info("RENDER TEMPLATE: %s -> %s", template, public) + self.render_template(template, public) + + def render_template(self, template, public): + """ + Render a template to the public path. + + Parameters: + template (str): path in ./templates + public (str): path in ./public + """ + tpl = self.env.get_template(template.replace("./templates/", "")) + html = tpl.render(self.vars()) + + with codecs.open(public, "w", "utf-8") as fh: + fh.write(html) + + + def crawl(self, path): + """ + Crawl a directory recursively and return a list of file paths. + + Parameters: + path (str) + + Returns: + filepaths (list[str]) + """ + for root, dirs, files in os.walk(path): + for file in files: + yield os.path.join(root, file) + + def should_copy_static(self, static_file, public_file): + """ + Determine if a static file should be copied over to the public folder. + + This means the public file either does not exist yet, or is older + than the static file and so the new static file should be copied over it. + + Parameters: + static_file (str) + public_file (str) + + Returns: + bool: True if public_file doesn't exist or is older than static_file. + """ + if not os.path.exists(public_file): + return True + return os.stat(static_file).st_mtime > os.stat(public_file).st_mtime + + def template_to_public(self, template_filename): + """ + Convert a template filename into a public name, preferring clean URL + paths without file extension suffixes. + + Examples: + * /index.html -> /index.html + * /about.html -> /about/index.html + * /photos/index.html -> /photos/index.html + * /photos/2018.html -> /photos/2018/index.html + + The `.html` and `.md` file types will suppress suffixes in this way. + + Parameters: + template_filename: like ``./templates/about.html`` + + Returns: + public_filename: like ``./public/about/index.html`` + """ + public = template_filename.replace("./templates/", "./public/") + path, filename = public.rsplit("/", 1) + basename, ext = filename.rsplit(".", 1) + if ext not in ["html", "md"]: + # Not a web page so just keep the path literal. + return public + + print("template_to_public:", template_filename, public) + + # See if it's already an index page or if we need to create one. + if filename in ["index.html", "index.md"]: + return public # already a good name + else: + print("HERE:", os.path.join(path, basename, "index."+ext)) + log.info("basename=%r path=%r filename=%s", basename, path, filename) + return os.path.join(path, basename, "index."+ext) + + def mkdir(self, path): + """Create a directory and log it.""" + if not os.path.isdir(path): + log.info("mkdir: %s", path) + os.makedirs(path, mode=0o755) + +if __name__ == "__main__": + App() diff --git a/static/images/photo.jpg b/static/images/photo.jpg new file mode 100644 index 0000000..e9b050b Binary files /dev/null and b/static/images/photo.jpg differ diff --git a/templates/contact.html b/templates/contact.html new file mode 100644 index 0000000..0b559f8 --- /dev/null +++ b/templates/contact.html @@ -0,0 +1,28 @@ +{% extends "layout/base.html" %} +{% block title %}Contact Me{% endblock %} +{% block content %} +
+

Contact Me

+ +

+ Below are some places you can find me on the Internet and ways you can + contact me. +

+ + + + + + +
Email: + Send me an e-mail here +
+ +

External Links

+ + +
+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..aede1c2 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,41 @@ +{% extends "layout/base.html" %} +{% block title %}Welcome{% endblock %} +{% block content %} +
+ + +

Hello world!

+ +

+ This is the personal homepage of Noah Petherbridge. +

+ +

+ I'm a software developer in California. I write about random stuff on + my web blog and + maintain a handful of open source projects on + GitHub + and git.kirsle.net. +

+ +

Software Development

+ +

+ I'm a full stack web developer and I program in Go, Python, and + JavaScript. One of my largest personal projects is + RiveScript, + a scripting language for programming chat bots — matching a user's + message to a certain response. +

+ +

+ My web blog, Kirsle.net, + is written in Go and I keep that source code here + for now. Previously it was written in + Python + and its HTML pages + are readable too. The site you're reading is a simple Python-generated + static site here. +

+
+{% endblock %} diff --git a/templates/layout/base.html b/templates/layout/base.html new file mode 100644 index 0000000..78ca0a4 --- /dev/null +++ b/templates/layout/base.html @@ -0,0 +1,25 @@ + + + + {% block title %}Untitled{% endblock %} - {{ site.name }} + + + + + +
+ +
+ +
+ {% block content %}{% endblock %} +
+ + + diff --git a/templates/theme/style.css b/templates/theme/style.css new file mode 100644 index 0000000..d35847a --- /dev/null +++ b/templates/theme/style.css @@ -0,0 +1,100 @@ +html, body { + height: 100%; + margin: 0; +} +body { + background-color: #0099FF; + font-family: Arial, Helvetica, sans-serif; + font-size: medium; + line-height: 1.6rem; + color: #222; + margin: 0 auto; + max-width: 950px; +} + +a:link, a:visited { + color: #006699; +} +a:hover, a:active { + color: #0099FF; +} + +h1 { + font-size: 24pt; +} +h2 { + font-size: 14pt; +} + +img { + max-width: 100%; + height: auto; +} +img.circle { + border-radius: 50%; +} + +header { + position: fixed; + top: 0; + left: 0; + right: 0; + line-height: 3rem; + background-color: rgba(255, 153, 255, 0.8); + box-shadow: 0px 2px 2px #000; +} +header h1 { + margin: 0; + font-size: larger; + font-style: italic; +} +header nav { + max-width: 860px; + margin: 0 auto; + padding: 0 2rem; + font-size: large; +} +header nav ul { + list-style: none; + float: right; + margin: 0; +} +header nav ul li { + display: inline; + padding-left: 8px; +} +header nav a { + color: #000 !important; + font-weight: bold; + text-decoration: none; +} + +main { + min-height: 100%; + margin: 0 24px; + background-color: #FFF; + box-shadow: 0px 0px 4px #000; +} +section { + padding: 2rem; +} +section.first { + padding-top: 4rem; +} + +footer { + max-width: 860px; + padding: 0 4rem; + margin: 2rem auto; + color: #036; + font-weight: bold; + text-align: center; + font-style: italic; +} + +.center { + display: block; + text-align: center; + margin-left: auto; + margin-right: auto; +} diff --git a/watch.sh b/watch.sh new file mode 100755 index 0000000..60b780d --- /dev/null +++ b/watch.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +function die() { + echo >&2 $1 + exit 1 +} + +# Before we crash and burn, make sure necessary programs are installed! +command -v inotifywait || die "I need the inotifywait command (apt install inotify-tools)" + +make +while inotifywait -r -e modify,move,create,delete static templates; do + make +done