Files
wren/util/generate_docs.py

200 lines
5.4 KiB
Python
Raw Permalink Normal View History

2017-10-12 06:38:34 -07:00
#!/usr/bin/env python3
2013-11-21 21:38:36 -08:00
import codecs
2013-11-21 21:38:36 -08:00
import glob
2015-01-18 15:36:36 -08:00
import fnmatch
2013-11-21 21:38:36 -08:00
import os
2017-10-12 06:38:34 -07:00
import posixpath
2013-11-21 21:38:36 -08:00
import shutil
2014-04-05 15:39:02 -07:00
import subprocess
2013-12-04 07:46:41 -08:00
import sys
import time
import re
2017-10-12 06:38:34 -07:00
import urllib
2013-11-21 21:38:36 -08:00
from datetime import datetime
2017-10-12 06:38:34 -07:00
from http.server import HTTPServer, SimpleHTTPRequestHandler
2013-11-21 21:38:36 -08:00
2015-01-18 15:36:36 -08:00
import markdown
# Match a "## " style header. We require a space after "#" to avoid
# accidentally matching "#include" in code samples.
MARKDOWN_HEADER = re.compile(r'#+ ')
# Clean up a header to be a valid URL.
2015-11-08 10:59:23 -08:00
FORMAT_ANCHOR = re.compile(r'\?|!|:|/|\*|`')
2017-10-12 06:38:34 -07:00
class RootedHTTPServer(HTTPServer):
"""Simple server that resolves paths relative to a given directory.
From: http://louistiao.me/posts/python-simplehttpserver-recipe-serve-specific-directory/
"""
def __init__(self, base_path, *args, **kwargs):
HTTPServer.__init__(self, *args, **kwargs)
self.RequestHandlerClass.base_path = base_path
class RootedHTTPRequestHandler(SimpleHTTPRequestHandler):
"""Simple handler that resolves paths relative to a given directory.
From: http://louistiao.me/posts/python-simplehttpserver-recipe-serve-specific-directory/
"""
def translate_path(self, path):
# Refresh files that are being requested.
format_files(True)
path = posixpath.normpath(urllib.parse.unquote(path))
words = path.split('/')
words = filter(None, words)
path = self.base_path
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir):
continue
path = os.path.join(path, word)
return path
2015-01-18 15:36:36 -08:00
def ensure_dir(path):
if not os.path.exists(path):
os.makedirs(path, exist_ok=True)
2015-01-18 15:36:36 -08:00
2014-01-31 20:55:37 -08:00
def is_up_to_date(path, out_path):
2013-12-04 07:46:41 -08:00
dest_mod = 0
if os.path.exists(out_path):
dest_mod = os.path.getmtime(out_path)
# See if it's up to date.
source_mod = os.path.getmtime(path)
2014-01-28 13:56:49 -06:00
return source_mod < dest_mod
2013-12-04 07:46:41 -08:00
2014-01-28 13:56:49 -06:00
def format_file(path, skip_up_to_date):
2015-01-18 15:36:36 -08:00
in_path = os.path.join('doc/site', path)
out_path = "build/docs/" + os.path.splitext(path)[0] + ".html"
template_path = os.path.join("doc/site", os.path.dirname(path),
"template.html")
2014-01-28 13:56:49 -06:00
if (skip_up_to_date and
is_up_to_date(in_path, out_path) and
is_up_to_date(template_path, out_path)):
2014-01-28 13:56:49 -06:00
# It's up to date.
2013-12-04 07:46:41 -08:00
return
2013-11-21 21:38:36 -08:00
title = ""
# Read the markdown file and preprocess it.
contents = ""
with codecs.open(in_path, "r", encoding="utf-8") as input:
2013-11-21 21:38:36 -08:00
# Read each line, preprocessing the special codes.
for line in input:
stripped = line.lstrip()
indentation = line[:len(line) - len(stripped)]
if stripped.startswith("^"):
command,_,args = stripped.rstrip("\n").lstrip("^").partition(" ")
args = args.strip()
if command == "title":
title = args
else:
print(' '.join(["UNKNOWN COMMAND:", command, args]))
2013-11-21 21:38:36 -08:00
elif MARKDOWN_HEADER.match(stripped):
2014-01-31 17:51:09 -08:00
# Add anchors to the headers.
index = stripped.find(" ")
headertype = stripped[:index]
header = stripped[index:].strip()
anchor = header.lower().replace(' ', '-')
anchor = FORMAT_ANCHOR.sub('', anchor)
2014-01-31 17:51:09 -08:00
contents += indentation + headertype
contents += '{1} <a href="#{0}" name="{0}" class="header-anchor">#</a>\n'.format(anchor, header)
2013-11-21 21:38:36 -08:00
else:
contents += line
2013-11-21 21:38:36 -08:00
html = markdown.markdown(contents, extensions=['def_list', 'smarty'])
2013-11-21 21:38:36 -08:00
# Use special formatting for example output and errors.
html = html.replace('<span class="c1">//&gt; ', '<span class="output">')
html = html.replace('<span class="c1">//&amp;gt; ', '<span class="output">')
html = html.replace('<span class="c1">//! ', '<span class="error">')
2015-01-18 15:36:36 -08:00
modified = datetime.fromtimestamp(os.path.getmtime(in_path))
2013-11-21 21:38:36 -08:00
mod_str = modified.strftime('%B %d, %Y')
with codecs.open(template_path, encoding="utf-8") as f:
page_template = f.read()
2015-01-18 15:36:36 -08:00
2014-04-14 21:23:46 -07:00
fields = {
'title': title,
'html': html,
'mod': mod_str
2014-04-14 21:23:46 -07:00
}
2013-11-21 21:38:36 -08:00
# Write the html output.
2015-01-18 15:36:36 -08:00
ensure_dir(os.path.dirname(out_path))
with codecs.open(out_path, "w", encoding="utf-8") as out:
2015-01-18 15:36:36 -08:00
out.write(page_template.format(**fields))
2013-11-21 21:38:36 -08:00
2017-10-12 06:38:34 -07:00
print("Built " + path)
2013-11-21 21:38:36 -08:00
def copy_static():
shutil.copy2("doc/site/blog/rss.xml", "build/docs/blog/rss.xml")
for root, dirnames, filenames in os.walk('doc/site/static'):
for filename in filenames:
source = os.path.join(root, filename)
source_mod = os.path.getmtime(source)
dest = os.path.join("build/docs", filename)
dest_mod = 0
if os.path.exists(dest):
dest_mod = os.path.getmtime('build/docs/style.css')
if source_mod < dest_mod:
return
shutil.copy2(source, dest)
print('Copied ' + filename)
2013-12-04 07:46:41 -08:00
def format_files(skip_up_to_date):
2014-04-05 15:39:02 -07:00
2015-01-18 15:36:36 -08:00
for root, dirnames, filenames in os.walk('doc/site'):
for filename in fnmatch.filter(filenames, '*.markdown'):
f = os.path.relpath(os.path.join(root, filename), 'doc/site')
format_file(f, skip_up_to_date)
2014-01-31 20:55:37 -08:00
copy_static()
2013-12-04 07:46:41 -08:00
2017-10-12 06:38:34 -07:00
def run_server():
port = 8000
handler = RootedHTTPRequestHandler
server = RootedHTTPServer("build/docs", ('localhost', port), handler)
print('Serving at port', port)
server.serve_forever()
2013-11-21 21:38:36 -08:00
# Clean the output directory.
2014-01-30 06:51:52 -08:00
if os.path.exists("build/docs"):
shutil.rmtree("build/docs")
2015-01-18 15:36:36 -08:00
ensure_dir("build/docs")
2013-11-21 21:38:36 -08:00
# Process each markdown file.
2013-12-04 07:46:41 -08:00
format_files(False)
2013-11-21 21:38:36 -08:00
2017-10-12 06:38:34 -07:00
# Watch and serve files.
if len(sys.argv) == 2 and sys.argv[1] == '--serve':
run_server()
2014-01-31 20:55:37 -08:00
# Watch files.
2013-12-04 07:46:41 -08:00
if len(sys.argv) == 2 and sys.argv[1] == '--watch':
while True:
format_files(True)
time.sleep(0.3)