vial.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: 53self.encoding = None 54with open(file_name, "rb") as f: 55self.content = f.read() 56 57 58class Index: 59def __init__(self, directory): 60self.directory = directory 61# Temporarily move to the specified directory in order to read the files. 62with in_directory(directory): 63self.file_names = [i for i in os.listdir() if os.path.isfile(i)] 64self.documents = [Document(i) for i in self.file_names] 65self.__current_index = 0 66 67def __iter__(self): 68return self 69 70def __next__(self): 71if self.__current_index >= len(self.documents): 72raise StopIteration 73else: 74self.__current_index += 1 75return self.documents[self.__current_index - 1] 76 77 78class Site: 79def __init__(self, build_dir): 80self.build_dir = build_dir 81self.template_engine = jinja2.Environment(loader=jinja2.FileSystemLoader("templates")) 82self.pages = {} 83 84def add_page(self, location, page): 85if location.endswith("/"): 86location += "index.html" 87location = location.lstrip("/") # interpret it as site root, not OS root 88self.pages[location] = page 89 90def add_from_index(self, index, location, template, static=False, **kwargs): 91location = location.lstrip("/") # interpret it as site root, not OS root 92if static: 93for document in index: 94self.pages[os.path.join(location, document.file_name)] = Static(self, document) 95else: 96for document in index: 97self.pages[os.path.join(location, document.file_name)] = Page(self, template, os.path.join(index.directory, document), **kwargs) 98 99def filter(self, name): 100def decorator(func): 101self.template_engine.filters[name] = func 102return func 103 104return decorator 105 106def build(self): 107# Clear the build directory if it exists. 108if os.path.isdir(self.build_dir): 109shutil.rmtree(self.build_dir) 110for location, page in self.pages.items(): 111# Create the required directories. 112os.makedirs(os.path.join(self.build_dir, os.path.dirname(location)), exist_ok=True) 113if isinstance(page, str): 114with open(os.path.join(self.build_dir, location), "w") as f: 115f.write(page) 116elif isinstance(page, bytes): 117with open(os.path.join(self.build_dir, location), "wb") as f: 118f.write(page) 119else: 120raise ValueError(f"{type(page)} cannot be used as a document") 121 122 123class Page(str): 124def __new__(cls, site, template, document, **kwargs): 125return site.template_engine.get_template(template).render(document=document, **kwargs) 126 127 128class Static(bytes): 129def __new__(cls, site, document): 130return document.content 131