143 lines
4.6 KiB
Python
Executable File
143 lines
4.6 KiB
Python
Executable File
#!/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()
|