__init__.py
Python script, ASCII text executable
1#!/usr/bin/env python3 2 3import os 4import jinja2 5from ruamel.yaml import YAML 6import shutil 7import contextlib 8 9 10@contextlib.contextmanager 11def in_directory(directory): 12cwd = os.getcwd() 13os.chdir(directory) 14try: 15yield 16finally: 17os.chdir(cwd) 18 19 20class Document: 21def __init__(self, file_name): 22self.file_name = file_name 23self.encoding = "utf-8" 24# If the file is text, read it. 25self.front_matter = YAML() 26try: 27with open(file_name, "r", encoding=self.encoding) as f: 28print("!" + f"Loading document {file_name}".center(80, "-") + "!") 29 30# Parse front matter if available. 31front_matter = "" 32initial_line = f.readline() 33if initial_line == "---\n": 34print("!" + "Front matter found".center(80, "-") + "!") 35line = "" 36while line != "---\n": 37line = f.readline() 38if line != "---\n": 39front_matter += line 40print("!" + "Front matter loaded".center(80, "-") + "!") 41print(front_matter) 42 43if front_matter: 44self.front_matter = self.front_matter.load(front_matter) 45 46print("!" + "Reading content".center(80, "-") + "!") 47 48self.content = initial_line + f.read() 49 50print("!" + "Content loaded".center(80, "-") + "!") 51print(self.content) 52except UnicodeDecodeError: 53print("!" + "Text decoding failed, assuming binary.".center(80, "-") + "!") 54self.encoding = None 55with open(file_name, "rb") as f: 56self.content = f.read() 57print("!" + "Binary content loaded".center(80, "-") + "!") 58 59 60class Index: 61def __init__(self, directory, recursive=False): 62self.directory = directory 63# Temporarily move to the specified directory in order to read the files. 64with in_directory(directory): 65if recursive: 66self.file_names = [os.path.join(dir_path, f) for dir_path, dir_name, filenames in os.walk(".") for f in filenames] 67else: 68self.file_names = [i for i in os.listdir() if os.path.isfile(i)] 69self.documents = [Document(i) for i in self.file_names] 70self.__current_index = 0 71 72def __iter__(self): 73return self 74 75def __next__(self): 76if self.__current_index >= len(self.documents): 77raise StopIteration 78else: 79self.__current_index += 1 80return self.documents[self.__current_index - 1] 81 82 83class Site: 84def __init__(self, build_dir, template_dir="templates"): 85self.build_dir = build_dir 86self.template_engine = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) 87self.pages = {} 88 89def add_page(self, location, page): 90if location.endswith("/"): 91location += "index.html" 92location = location.lstrip("/") # interpret it as site root, not OS root 93self.pages[location] = page 94 95def add_from_index(self, index, location, template, static=False, **kwargs): 96location = location.lstrip("/") # interpret it as site root, not OS root 97if static: 98for document in index: 99self.pages[os.path.join(location, document.file_name)] = Static(self, document) 100else: 101for document in index: 102self.pages[os.path.join(location, document.file_name)] = Page(self, template, os.path.join(index.directory, document), **kwargs) 103 104def filter(self, name): 105def decorator(func): 106self.template_engine.filters[name] = func 107return func 108 109return decorator 110 111def build(self): 112# Clear the build directory if it exists. 113if os.path.isdir(self.build_dir): 114shutil.rmtree(self.build_dir) 115for location, page in self.pages.items(): 116# Create the required directories. 117os.makedirs(os.path.join(self.build_dir, os.path.dirname(location)), exist_ok=True) 118if isinstance(page, str): 119with open(os.path.join(self.build_dir, location), "w") as f: 120f.write(page) 121elif isinstance(page, bytes): 122with open(os.path.join(self.build_dir, location), "wb") as f: 123f.write(page) 124else: 125raise ValueError(f"{type(page)} cannot be used as a document") 126 127 128class Page(str): 129def __new__(cls, site, template, document, **kwargs): 130return site.template_engine.get_template(template).render(document=document, **kwargs) 131 132 133class Static(bytes): 134def __new__(cls, site, document): 135return document.content 136