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