By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 __init__.py

View raw Download
text/x-script.python • 6.35 kiB
Python script, ASCII text executable
        
            
1
#!/usr/bin/env python3
2
3
import os
4
import jinja2
5
from ruamel.yaml import YAML
6
import shutil
7
import contextlib
8
import colorama
9
10
11
colorama.init()
12
13
14
@contextlib.contextmanager
15
def in_directory(directory):
16
cwd = os.getcwd()
17
os.chdir(directory)
18
try:
19
yield
20
finally:
21
os.chdir(cwd)
22
23
24
def delete_directory_contents(directory):
25
for root, dirs, files in os.walk(directory):
26
for file in files:
27
os.remove(os.path.join(root, file))
28
for dir in dirs:
29
shutil.rmtree(os.path.join(root, dir))
30
31
32
class Document:
33
def __init__(self, file_name, url_transform=lambda x: x):
34
self.file_name = file_name
35
self.encoding = "utf-8"
36
# If the file is text, read it.
37
self.front_matter = YAML()
38
self.content = ""
39
try:
40
with open(file_name, "r", encoding=self.encoding) as f:
41
print(colorama.Style.RESET_ALL, colorama.Style.BRIGHT, colorama.Fore.LIGHTWHITE_EX, f"Loading document {file_name}".ljust(shutil.get_terminal_size().columns), sep="")
42
43
# Parse front matter if available.
44
front_matter = ""
45
initial_line = f.readline()
46
if initial_line == "---\n":
47
print(colorama.Style.RESET_ALL, colorama.Fore.CYAN, "Front matter found", sep="")
48
line = ""
49
while line != "---\n":
50
line = f.readline()
51
if line != "---\n":
52
front_matter += line
53
print(colorama.Style.RESET_ALL, colorama.Fore.GREEN, "Front matter loaded", sep="")
54
print(front_matter)
55
56
if front_matter:
57
self.front_matter = self.front_matter.load(front_matter)
58
else: # put it back
59
self.content = initial_line
60
61
print(colorama.Style.RESET_ALL, colorama.Fore.CYAN, "Reading content", sep="")
62
63
self.content += f.read()
64
65
print(colorama.Style.RESET_ALL, colorama.Fore.GREEN, "Content loaded", sep="")
66
print(colorama.Style.RESET_ALL, colorama.Style.DIM, self.content[:128] + "..." if len(self.content) > 128 else self.content)
67
except UnicodeDecodeError:
68
print(colorama.Style.RESET_ALL, colorama.Fore.CYAN, "Text decoding failed, assuming binary", sep="")
69
self.encoding = None
70
with open(file_name, "rb") as f:
71
self.content = f.read()
72
print(colorama.Style.RESET_ALL, colorama.Fore.GREEN, "Binary content loaded", sep="")
73
74
print(colorama.Style.RESET_ALL, colorama.Fore.CYAN, colorama.Style.DIM, f"Transforming URL {self.file_name} ->", end=" ", sep="")
75
self.file_name = url_transform(self.file_name)
76
print(colorama.Style.RESET_ALL, colorama.Style.BRIGHT, colorama.Fore.LIGHTYELLOW_EX, self.file_name)
77
78
print(colorama.Style.RESET_ALL, end="")
79
80
def __repr__(self):
81
return f"Document({self.file_name})"
82
83
def __str__(self):
84
return self.content
85
86
def __getitem__(self, item):
87
return self.front_matter[item]
88
89
90
class Index:
91
def __init__(self, directory, recursive=False, url_transform=lambda x: x):
92
self.directory = directory
93
# Temporarily move to the specified directory in order to read the files.
94
with in_directory(directory):
95
if recursive:
96
self.file_names = [os.path.join(dir_path, f) for dir_path, dir_name, filenames in os.walk(".") for f in filenames]
97
else:
98
self.file_names = [i for i in os.listdir() if os.path.isfile(i)]
99
self.documents = [Document(i, url_transform) for i in self.file_names]
100
self.__current_index = 0
101
102
def __iter__(self):
103
self.__current_index = 0
104
return self
105
106
def __next__(self):
107
if self.__current_index >= len(self.documents):
108
raise StopIteration
109
else:
110
self.__current_index += 1
111
return self.documents[self.__current_index - 1]
112
113
def __repr__(self):
114
return f"Index({self.directory}): {self.documents}"
115
116
def __len__(self):
117
return len(self.documents)
118
119
120
class Site:
121
def __init__(self, build_dir, template_dir="templates"):
122
self.build_dir = build_dir
123
self.template_engine = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir))
124
self.pages = {}
125
self.context = {}
126
127
def add_page(self, location, page):
128
if location.endswith("/"):
129
location += "index.html"
130
location = location.lstrip("/") # interpret it as site root, not OS root
131
self.pages[location] = page
132
133
def add_from_index(self, index, location, template, static=False, **kwargs):
134
location = location.lstrip("/") # interpret it as site root, not OS root
135
kwargs = {**self.context, **kwargs}
136
if static:
137
for document in index:
138
self.pages[os.path.join(location, document.file_name)] = Static(self, document)
139
else:
140
for document in index:
141
self.pages[os.path.join(location, document.file_name)] = Page(self, template, document, **kwargs)
142
143
def filter(self, name):
144
def decorator(func):
145
self.template_engine.filters[name] = func
146
return func
147
148
return decorator
149
150
def build(self):
151
# Clear the build directory if it exists.
152
if os.path.isdir(self.build_dir):
153
delete_directory_contents(self.build_dir)
154
for location, page in self.pages.items():
155
# Create the required directories.
156
os.makedirs(os.path.join(self.build_dir, os.path.dirname(location)), exist_ok=True)
157
if isinstance(page, str):
158
with open(os.path.join(self.build_dir, location), "w") as f:
159
f.write(page)
160
elif isinstance(page, bytes):
161
with open(os.path.join(self.build_dir, location), "wb") as f:
162
f.write(page)
163
else:
164
raise ValueError(f"{type(page)} cannot be used as a document")
165
166
167
class Page(str):
168
def __new__(cls, site, template, document=None, **kwargs):
169
kwargs = {**site.context, **kwargs}
170
return site.template_engine.get_template(template).render(document=document, **kwargs)
171
172
173
class Static(bytes):
174
def __new__(cls, site, document):
175
return document.content
176