#!/usr/bin/env python import codecs import glob import fnmatch import os import shutil import subprocess import sys import time import re from datetime import datetime 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. FORMAT_ANCHOR = re.compile(r'\?|!|:|/|\*|`') def ensure_dir(path): if not os.path.exists(path): os.mkdir(path) def is_up_to_date(path, out_path): 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) return source_mod < dest_mod def format_file(path, skip_up_to_date): 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") if (skip_up_to_date and is_up_to_date(in_path, out_path) and is_up_to_date(template_path, out_path)): # It's up to date. return title = "" # Read the markdown file and preprocess it. contents = "" with codecs.open(in_path, "r", encoding="utf-8") as input: # 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])) elif MARKDOWN_HEADER.match(stripped): # Add anchors to the headers. index = stripped.find(" ") headertype = stripped[:index] header = stripped[index:].strip() anchor = header.lower().replace(' ', '-') anchor = FORMAT_ANCHOR.sub('', anchor) contents += indentation + headertype contents += '{1} #\n'.format(anchor, header) else: # Forcibly add a space to the end of each line. Works around a bug in # the smartypants extension that removes some newlines that are needed. # https://github.com/waylan/Python-Markdown/issues/439 if "//" not in line: contents = contents + line.rstrip() + ' \n' else: # Don't add a trailing space on comment lines since they may be # output lines which have a trailing ">" which makes the extra space # visible. contents += line html = markdown.markdown(contents, ['def_list', 'codehilite', 'smarty']) # Use special formatting for example output and errors. html = html.replace('//> ', '') html = html.replace('//! ', '') modified = datetime.fromtimestamp(os.path.getmtime(in_path)) mod_str = modified.strftime('%B %d, %Y') with codecs.open(template_path, encoding="utf-8") as f: page_template = f.read() fields = { 'title': title, 'html': html, 'mod': mod_str } # Write the html output. ensure_dir(os.path.dirname(out_path)) with codecs.open(out_path, "w", encoding="utf-8") as out: out.write(page_template.format(**fields)) print("converted " + path) def check_sass(): source_mod = os.path.getmtime('doc/site/style.scss') dest_mod = 0 if os.path.exists('build/docs/style.css'): dest_mod = os.path.getmtime('build/docs/style.css') if source_mod < dest_mod: return subprocess.call(['sass', 'doc/site/style.scss', 'build/docs/style.css']) print("built css") def format_files(skip_up_to_date): check_sass() 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) # Clean the output directory. if os.path.exists("build/docs"): shutil.rmtree("build/docs") ensure_dir("build/docs") # Process each markdown file. format_files(False) # Watch files. if len(sys.argv) == 2 and sys.argv[1] == '--watch': while True: format_files(True) time.sleep(0.3)