1 | #!/usr/bin/env python3 |
2 | """Open source template engine for compilation of the static joshstock.in""" |
3 |
|
4 | import sys |
5 | import os |
6 | import shutil |
7 | import json |
8 | import markdown2 |
9 | import readtime |
10 |
|
11 |
|
12 | def file_read(filename: str): |
13 | """Read text from file by filename""" |
14 | try: |
15 | with open(filename, "r") as file: |
16 | return file.read() |
17 | except FileNotFoundError: |
18 | print(f"[file_read] '{filename}' not found. exiting...") |
19 | exit(1) |
20 | except Exception as error: |
21 | print(f"[file_read] error while trying to read from '{filename}':") |
22 | print(error) |
23 | print("[file_read] exiting...") |
24 | exit(1) |
25 |
|
26 |
|
27 | def file_write(filename: str, text: str): |
28 | """Write text to file by filename""" |
29 | try: |
30 | with open(filename, "w") as file: |
31 | file.write(text) |
32 | except Exception as error: |
33 | print(f"[file_write] error while trying to write to '{filename}':") |
34 | print(error) |
35 | print("[file_write] exiting...") |
36 | exit(1) |
37 |
|
38 |
|
39 | def directory_empty(path: str): |
40 | """Clear file directory by path""" |
41 | for file in os.listdir(path): |
42 | filepath = os.path.join(path, file) |
43 | try: |
44 | if os.path.isfile(filepath): |
45 | os.unlink(filepath) |
46 | elif os.path.isdir(filepath): |
47 | shutil.rmtree(filepath) |
48 | except Exception as error: |
49 | print(f"[directory_empty] error while trying to empty directory {path}:") |
50 | print(error) |
51 | print("[directory_empty] exiting...") |
52 | exit(1) |
53 |
|
54 |
|
55 | ROUTEMAP = {} |
56 | TEMPLATES = {} |
57 |
|
58 |
|
59 | def template_fill(template_string: str, template_keys: dict): |
60 | """Fills in all template key placeholders in template_string""" |
61 | global TEMPLATES |
62 | return_string = template_string |
63 | did_fill = False |
64 | for key in template_keys: |
65 | if f"${key}" in return_string: |
66 | return_string = return_string.replace(f"${key}", template_keys[key]) |
67 | did_fill = True |
68 | for key in TEMPLATES: |
69 | if f"${key}" in return_string: |
70 | return_string = return_string.replace(f"${key}", TEMPLATES[key]) |
71 | did_fill = True |
72 | if did_fill: |
73 | return_string = template_fill(return_string, template_keys) |
74 | return return_string |
75 |
|
76 |
|
77 | def templates_load(templates_config: dict): |
78 | """Preload templates from their files""" |
79 | templates = {} |
80 | for temp in templates_config: |
81 | print(f"[templates_load] loading template '{temp}'") |
82 | templates[temp] = file_read(templates_config[temp]) |
83 | return templates |
84 |
|
85 |
|
86 | def template(output_path: str): |
87 | """The main template engine to generate the site's static content""" |
88 | global TEMPLATES |
89 | global ROUTEMAP |
90 | print("[template] emptying working directory") |
91 | directory_empty(output_path) |
92 |
|
93 | print("[template] reading config file at ./config.json") |
94 | config = json.loads(file_read("config.json")) |
95 |
|
96 | print("[template] copying static directory") |
97 | output_file = os.path.join(output_path, "static") |
98 | shutil.copytree(config["static_directory"], output_file) |
99 |
|
100 | print("[template] loading templates from config") |
101 | TEMPLATES = templates_load(config["templates"]) |
102 |
|
103 | print("[template] running blog article generator") |
104 | blog_article_listings = "" |
105 | for article in config["articles"]: |
106 | article_url = f"/blog/{article['identifier']}" |
107 | print(f"[template/blog] creating article '{article['title']}' at {article_url}") |
108 |
|
109 | content = markdown2.markdown(file_read(article["markdown"])) |
110 | content_time = str(readtime.of_html(content)) |
111 |
|
112 | # Create a new listing for the blog archive page |
113 | blog_article_listings += template_fill( |
114 | TEMPLATES["blog-listing"], |
115 | { |
116 | "title": article["title"], |
117 | "datestring": article["datestring"], |
118 | "readtime": content_time, |
119 | "banner": article["banner"], |
120 | "description": article["description"], |
121 | "permalink": article_url, |
122 | }, |
123 | ) |
124 |
|
125 | # Create blog article from template |
126 | blog_article = template_fill( |
127 | TEMPLATES["blog-article"], |
128 | { |
129 | "title": article["title"], |
130 | "datestring": article["datestring"], |
131 | "readtime": content_time, |
132 | "banner": article["banner"], |
133 | "description": article["description"], |
134 | "permalink": article_url, |
135 | "content": content, |
136 | }, |
137 | ) |
138 | output_file = os.path.join(output_path, f"blog-{article['identifier']}.html") |
139 | file_write(output_file, blog_article) |
140 | ROUTEMAP[f"{config['domain']}{article_url}"] = 0.7 |
141 |
|
142 | TEMPLATES["@blog-listings"] = blog_article_listings |
143 |
|
144 | print("[template] running page generator") |
145 | for page in config["pages"]: |
146 | page_url = page["location"] |
147 | print(f"[template/page] creating page '{page['title']}' at {page_url}") |
148 | content = template_fill( |
149 | file_read(page["file"]), |
150 | { |
151 | "title": page["title"], |
152 | "description": page["description"], |
153 | "permalink": page_url, |
154 | }, |
155 | ) |
156 | output_file = os.path.join(output_path, page["destination"]) |
157 | file_write(output_file, content) |
158 | ROUTEMAP[f"{config['domain']}{page_url}"] = page["priority"] |
159 |
|
160 | print("[template] copying custom static files") |
161 | for copy in config["copy"]: |
162 | print(f"[template/copy] copying file '{copy['file']}' to '{copy['location']}'") |
163 | output_file = os.path.join(output_path, copy["location"]) |
164 | shutil.copy(copy["file"], output_file) |
165 |
|
166 | print("[template] compiling sitemap XML") |
167 | sitemap = TEMPLATES["sitemap"] |
168 | for route in ROUTEMAP: |
169 | sitemap += ( |
170 | f"<url><loc>{route}</loc><priority>{ROUTEMAP[route]}</priority></url>" |
171 | ) |
172 | sitemap += "</urlset>" |
173 | output_file = os.path.join(output_path, "sitemap.xml") |
174 | file_write(output_file, sitemap) |
175 |
|
176 | print("[template] finished") |
177 |
|
178 |
|
179 | if __name__ == "__main__": |
180 | if len(sys.argv) < 2: |
181 | FOLDER_OUT = "/var/www/html" |
182 | else: |
183 | FOLDER_OUT = sys.argv[1] |
184 | print(f"[main] compile.py starting") |
185 | print(f"[main] changing active directory to script location") |
186 | os.chdir(sys.path[0]) |
187 | if not os.path.isdir(FOLDER_OUT): |
188 | print(f"[main] {FOLDER_OUT} is not a valid folder location. exiting...") |
189 | exit(1) |
190 | OUTPUT_PATH = os.path.abspath(FOLDER_OUT) |
191 | print(f"[main] output path set to {OUTPUT_PATH}") |
192 | print(f"[main] running template engine routine") |
193 | template(OUTPUT_PATH) |
194 | print(f"[main] finished. exiting...") |
195 | exit(0) |
196 | else: |
197 | print(f"[main] script is not __main__. exiting...") |
198 | exit(1) |
199 |
|