295 lines
8.2 KiB
Python
295 lines
8.2 KiB
Python
import subprocess
|
||
import os
|
||
from pathlib import Path
|
||
import shutil
|
||
import csv
|
||
import re
|
||
from datetime import datetime
|
||
|
||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||
import frontmatter
|
||
from slugify import slugify
|
||
import pypandoc
|
||
|
||
import xml.etree.ElementTree as ET
|
||
|
||
# TODO make newsletter URL's absolute to klank.school
|
||
env = Environment(
|
||
loader=PackageLoader("src"),
|
||
autoescape=select_autoescape()
|
||
)
|
||
|
||
CONTENT_D = os.path.abspath("src/content")
|
||
OUTPUT_D = "dist"
|
||
OUT_ASSETS = "dist/assets"
|
||
SRC_ASSETS = "src/assets"
|
||
documents = {}
|
||
now = datetime.now()
|
||
word_count = 0
|
||
|
||
# Utils
|
||
def getParam(params, index):
|
||
return params[index] if len(params) > index else False
|
||
|
||
|
||
# jinja filter that can list documents
|
||
def listDocuments(params):
|
||
param = params.split(" ")
|
||
template = env.select_template(["snippets/list-documents.jinja"])
|
||
html = template.render(documents=documents, layout=param[0], type=param[1])
|
||
|
||
return html
|
||
|
||
# jinja filter to make a slug out of a stirng
|
||
def slugify_filter(value):
|
||
return slugify(value)
|
||
|
||
# Source: https://github.com/gandreadis/markdown-word-count
|
||
def count_words_in_markdown(text):
|
||
|
||
# Comments
|
||
text = re.sub(r'<!--(.*?)-->', '', text, flags=re.MULTILINE)
|
||
# Tabs to spaces
|
||
text = text.replace('\t', ' ')
|
||
# More than 1 space to 4 spaces
|
||
text = re.sub(r'[ ]{2,}', ' ', text)
|
||
# Footnotes
|
||
text = re.sub(r'^\[[^]]*\][^(].*', '', text, flags=re.MULTILINE)
|
||
# Indented blocks of code
|
||
text = re.sub(r'^( {4,}[^-*]).*', '', text, flags=re.MULTILINE)
|
||
# Replace newlines with spaces for uniform handling
|
||
text = text.replace('\n', ' ')
|
||
# Custom header IDs
|
||
text = re.sub(r'{#.*}', '', text)
|
||
# Remove images
|
||
text = re.sub(r'!\[[^\]]*\]\([^)]*\)', '', text)
|
||
# Remove HTML tags
|
||
text = re.sub(r'</?[^>]*>', '', text)
|
||
# Remove special characters
|
||
text = re.sub(r'[#*`~\-–^=<>+|/:]', '', text)
|
||
# Remove footnote references
|
||
text = re.sub(r'\[[0-9]*\]', '', text)
|
||
# Remove enumerations
|
||
text = re.sub(r'[0-9#]*\.', '', text)
|
||
|
||
return len(text.split())
|
||
|
||
# jinja filter for date formatting
|
||
def prettydate(value, format='%d/%m/%Y'):
|
||
return datetime.fromtimestamp(int(value)).strftime(format)
|
||
|
||
|
||
# jinja filter to replace shortcodes in HTML
|
||
def shortcode_filter(value):
|
||
|
||
shortcode_callbacks = {
|
||
"show": listDocuments
|
||
}
|
||
|
||
def shortcode_replacer(match):
|
||
|
||
shortcode_name = match.group(1).strip()
|
||
param = match.group(2).strip()
|
||
|
||
if shortcode_name in shortcode_callbacks:
|
||
return shortcode_callbacks[shortcode_name](param)
|
||
|
||
return match.group(0)
|
||
|
||
pattern = re.compile(r"{{\s*(\w+)\s+([^{}]+?)\s*}}")
|
||
return pattern.sub(shortcode_replacer, value)
|
||
|
||
|
||
env.filters["shortcode"] = shortcode_filter
|
||
env.filters["slugify"] = slugify_filter
|
||
env.filters["prettydate"] = prettydate
|
||
|
||
# translate a single file into HTML
|
||
def render_single_file(page, path, dist, name = False):
|
||
name = Path(path).stem
|
||
template = env.select_template([f"{name}.jinja", "post.jinja"])
|
||
html = template.render(documents=documents, page=page, name=name)
|
||
|
||
if not os.path.exists(dist):
|
||
os.makedirs(dist)
|
||
|
||
with open(f"{dist}/{name}.html", "w", encoding="utf-8") as output_file:
|
||
output_file.write(html)
|
||
|
||
|
||
# find a pre-rendered page
|
||
def get_existing_page(path, slug):
|
||
stem = Path(path).stem
|
||
folder = os.path.basename(os.path.dirname(path))
|
||
|
||
if stem == "index" and folder != "content":
|
||
folder = Path(path).parent.parent.name
|
||
|
||
if slug in documents:
|
||
return documents[slug]
|
||
|
||
if folder == "content":
|
||
return False
|
||
|
||
for doc in documents[folder]:
|
||
if doc:
|
||
if doc["slug"] == slug:
|
||
return doc
|
||
|
||
return False
|
||
|
||
# build a slug including the folder
|
||
def get_slug(path, folder, filename):
|
||
if folder == "content":
|
||
return slugify(filename)
|
||
else:
|
||
return slugify(f"{folder}/{filename}")
|
||
|
||
# compile markdown into cited HTML
|
||
def get_page_data(path):
|
||
global word_count
|
||
|
||
filename = Path(path).stem
|
||
folder = Path(path).parent.name
|
||
slug = get_slug(path, folder, filename)
|
||
prerendered = get_existing_page(path, slug)
|
||
|
||
if prerendered:
|
||
return prerendered
|
||
|
||
page = frontmatter.load(path)
|
||
page["slug"] = slug
|
||
page["filename"] = filename
|
||
page["folder"] = folder
|
||
|
||
if "start_datetime" in page:
|
||
page["has_passed"] = datetime.fromtimestamp(page["start_datetime"]) < now
|
||
|
||
content = page.content
|
||
|
||
if "`include" in page.content:
|
||
content = pypandoc.convert_text(
|
||
page.content,
|
||
to='md',
|
||
format='md',
|
||
extra_args=[
|
||
"--lua-filter=include-files.lua"
|
||
])
|
||
|
||
page.body = pypandoc.convert_text(
|
||
content,
|
||
to="html",
|
||
format="md",
|
||
extra_args=[
|
||
"--citeproc",
|
||
"--bibliography=library.bib",
|
||
"--csl=harvard-cite-them-right.csl",
|
||
])
|
||
|
||
word_count += count_words_in_markdown(page.body)
|
||
|
||
return page
|
||
|
||
# Do stuff to the circuit's pcb
|
||
def save_circuit_svg(filepath, outpath, name):
|
||
|
||
tree = ET.parse(filepath)
|
||
root = tree.getroot()
|
||
|
||
# Extract current width/height (in pixels)
|
||
width_px = float(root.get("width", 0))
|
||
height_px = float(root.get("height", 0))
|
||
|
||
# Set new width/height in mm
|
||
root.set("width", f"{width_px}mm")
|
||
root.set("height", f"{height_px}mm")
|
||
|
||
os.makedirs(outpath, exist_ok = True)
|
||
|
||
tree.write(f"{outpath}/{name}")
|
||
|
||
|
||
# combine HTML & data with Jinja templates
|
||
def render_posts(path, output_path=OUTPUT_D):
|
||
name = Path(path).stem
|
||
|
||
for filename in sorted(os.listdir(path)):
|
||
file_path = Path(path) / filename
|
||
|
||
if file_path.suffix == ".md":
|
||
render_single_file(get_page_data(file_path), file_path, f"{output_path}/{name}")
|
||
elif file_path.is_dir():
|
||
render_posts(file_path, f"{output_path}/{name}")
|
||
elif file_path.suffix == ".svg":
|
||
save_circuit_svg(file_path, f"{output_path}/{name}", filename)
|
||
elif file_path.suffix in {".jpeg", ".mp3", ".jpg", ".png"}:
|
||
os.makedirs(f"{output_path}/{name}", exist_ok = True)
|
||
shutil.copyfile(file_path, f"{output_path}/{name}/{filename}")
|
||
|
||
# Pre-load before compiling
|
||
def preload_documents():
|
||
global documents
|
||
|
||
version = subprocess.check_output(["git", "rev-list", "--count", "HEAD"]).decode("utf-8").strip()
|
||
|
||
|
||
documents["meta"] = {"now": now.strftime("%d %B %Y"), "version": version}
|
||
|
||
for subdir in os.listdir(CONTENT_D):
|
||
path = os.path.join(CONTENT_D, subdir)
|
||
|
||
if os.path.isdir(path):
|
||
name = Path(path).stem
|
||
documents.setdefault(name, [])
|
||
|
||
for filename in sorted(os.listdir(path)):
|
||
cpath = os.path.join(path, filename)
|
||
if filename.endswith(".md"):
|
||
documents[name].append(get_page_data(cpath))
|
||
elif os.path.isdir(cpath):
|
||
documents[name].append(get_page_data(os.path.join(cpath, "index.md")))
|
||
|
||
elif Path(path).suffix == '.md':
|
||
documents[Path(path).stem] = get_page_data(path)
|
||
|
||
|
||
def copy_assets():
|
||
if os.path.exists(OUT_ASSETS):
|
||
shutil.rmtree(OUT_ASSETS)
|
||
|
||
shutil.copytree(SRC_ASSETS, OUT_ASSETS)
|
||
|
||
|
||
def get_inventory():
|
||
global documents
|
||
|
||
with open("src/content/component-inventory.csv") as f:
|
||
documents['inventory'] = []
|
||
|
||
for line in csv.DictReader(f, fieldnames=('ID', 'Amount', 'Name', 'Value', 'type', 'Date', 'Where','Mounting type')):
|
||
documents['inventory'].append(line)
|
||
|
||
|
||
def main():
|
||
print("....Start putting together a new document....")
|
||
get_inventory()
|
||
preload_documents()
|
||
|
||
for subdir in os.listdir(CONTENT_D):
|
||
path = os.path.join(CONTENT_D, subdir)
|
||
|
||
if os.path.isdir(path):
|
||
print("Compile: an entire directory", Path(path).name)
|
||
render_posts(path)
|
||
elif Path(path).suffix == '.md':
|
||
print("Compile: single page", Path(path).name)
|
||
render_single_file(get_page_data(path), path, OUTPUT_D)
|
||
elif Path(path).suffix in [".csv"]:
|
||
print("Compile: not compiling ", Path(path).name)
|
||
|
||
copy_assets()
|
||
|
||
print(f"total words: {word_count}")
|
||
|
||
main()
|