I've got my own website! Woo! It's got HTML and everything! But after struggling for a while with HTML and keeping my navbar consistent across pages, I gave up and wrote a program which:
- Searches for every markdown file in the given root directory or below.
- Parses it into HTML with mistune, which thankfully only needs the one file to work
- Inserts it into a given template file which contains stuff like a navbar and a footer
- Looks for a similarly-named
.js
or.css
file and links those if appropriate - Looks for a
page-title
thing at the beginning and sets the title to that if it exists - Puts it all together into a
.html
file - Searches through the
.html
file for any URLs which point to the root of the site and modifies them to point to the real root if, like me, your content is hosted in a subdirectory - Writes all that fun stuff to the hard drive for Apache to read
In short, it mashes everything together for a website that actually looks pretty good.
# Configuration options:
# =====================
# Where are the files to parse located? (generally ~/www)
# Don't include the trailing slash.
ROOT_PATH = "/home/MY_USERNAME/www"
# Is your site hosted at some prefix? Set ROOT_URL to wherever
# that is and all the URLs which start from root (i.e. start
# with "/") will have it prepended. No trailing slash.
ROOT_URL = "/MY_USERNAME"
# Suppress logging if in fast mode to speed it up some
DEBUG_MODE = False
# ================ #
# Code starts here #
# Do not modify it #
# ================ #
# Imports not at the top because that way it's JUST configuration
# stuff up there.
from glob import glob
import mistune, os, re, sys, time
def log(a):
if DEBUG_MODE:
print(a)
def root_sub(m):
url = m.group(0)
if url.startswith(ROOT_URL):
print(" Found URL starting with site root! Change it!")
return url
if url.startswith("/"):
log(" Prepending site root to URL")
return ROOT_URL + url
else:
log(" URL is relative; not prepending site root")
return url
def parse(page, style_path, script_path):
# Page styles
style_tag = ""
if style_path:
log(" Adding style path to file")
style_tag = "<link href=\"{}\" rel=\"stylesheet\" />"\
.format(os.path.basename(style_path))
else:
log(" No style file found")
# Page scripts
script_tag = ""
if script_path:
log(" Adding script path to file")
script_tag = "<script src=\"{}\" />"\
.format(os.path.basename(script_path))
else:
log(" No script file found")
# Page title
match = re.search(r"^page-title: ?(.*?)$", page, re.M)
if match:
log(" Extracting title")
title_tag = "<title>" + match.group(1) + "</title>"
page = page[:match.start()] + page[match.end():]
else:
log(" No title found; defaulting to 'Untitled Page'")
title_tag = "<title>Untitled Page</title>"
# Writing the data
log(" Parsing markdown")
parsed = md(page)
log(" Performing template replacement")
templated = (template # This odd formatting makes it easy to reorder/add
.replace("[[version]]", version)
.replace("[[style-tag]]", style_tag)
.replace("[[script-tag]]", script_tag)
.replace("[[title-tag]]", title_tag)
.replace("[[markdown]]", parsed)
)
if (ROOT_URL and ROOT_URL != "/"):
log(" Performing link modification...")
templated = re.sub(r'href="/', r'href="{}/'.format(ROOT_URL),
templated)
log(" Done")
else:
log(" Root is just the normal root; no prepending needed.")
return templated
# Setup
version = time.strftime("%H:%M:%S %Y-%m-%d")
print("Publishing version {}".format(version))
md = mistune.Markdown()
files = list(glob(ROOT_PATH + "/**/*.md")) + list(glob(ROOT_PATH + "/*.md"))
template_path = "./_template.html"
log("Template file at {}".format(template_path))
log("Files to parse: {}".format(", ".join(files)))
log("Reading template file data")
with open(template_path) as template_file:
template = template_file.read()
for i in range(0, len(files)):
# Per-file setup
path = files[i]
path_base = path[0:-3] # This is safe because we know it's always .md
out_path = path_base + ".html"
if not DEBUG_MODE:
print("Parsing {}/{}: {}".format(i + 1, len(files), path))
log("Parsing {} -> {}...".format(path, out_path))
log(" Calculating paths")
style_path = path_base + ".css"
log(" Searching for stylesheet at {}".format(style_path))
script_path = path_base + ".js"
log(" Searching for scripts at {}".format(script_path))
# Markdown reading
log(" Reading Markdown from file...")
log(" Opening file for reading")
with open(path, "r") as parsing:
log(" Reading data")
raw = parsing.read()
log(" Done")
parsed = parse(raw,
style_path if os.path.isfile(style_path) else None,
script_path if os.path.isfile(script_path) else None)
log(" Writing output to file...")
log(" Opening file for output")
with open(out_path, "w") as out:
log(" Writing data")
out.write(parsed)
log(" Done")
log("Done")
print("Completed parsing for version {}.".format(version))
I'm mostly concerned about
- Efficiency -- Both in terms of speed and memory. I'm especially concerned about the constant calls to
log
-- if it's not in debug mode, do these have any overhead, or does Python ignore them because it can tell that the function doesn't do anything? - Readability -- I tried to make the code understandable, but in places it's confusing.
- Fewer long, long blocks of code -- like the
parse
function, which I can't figure out how to reasonably split into pieces. - Safety -- Does my URL replacement only happen when I expect it to (i.e. in URLs, or stuff that looks reasonably like one)? What about my
page-title
searching?
I'm not at all concerned about
- Perfect compliance with PEP8 -- there are a bunch of guidelines that I simply don't care about, because they don't make sense to me. Feel free to point out violations, but I've run this through a validator and fixed the things I cared about, so anything left over is irrelevant to me.
- Cross-platform functioning -- this is a nice bonus, but frankly unnecessary, since I'll probably never use a non-Unix box.
Note: I'm strictly limited to Python 2.7.5 by the server I'm working on -- I can't install 3 or even a newer version of 2.