vite/vite.py (view raw)
1# vite - a simple and minimal static site generator, that JustWorks™
2# Copyright (c) 2020 Anirudh Oppiliappan <x@icyphox.sh>
3# Licensed under the MIT license
4
5import sys
6import pathlib
7import os
8import jinja2
9import time
10import http.server
11import socketserver
12import shutil
13import datetime
14import re
15
16from myrkdown import markdown_path
17from huepy import *
18from livereload import Server
19from subprocess import call
20
21
22# constants
23PAGES_PATH = "pages/"
24BUILD_PATH = "build/"
25TEMPL_PATH = "templates/"
26TEMPL_FILE = ""
27PORT = 1911
28
29
30def import_config():
31 try:
32 sys.path.append(os.getcwd())
33 globals()["config"] = __import__("config")
34 global TEMPL_FILE
35 TEMPL_FILE = os.path.join(TEMPL_PATH, config.template)
36 except ImportError:
37 print(bad("Error: config.py not found."))
38 print(que("Are you sure you're in a project directory?"))
39 sys.exit(1)
40
41
42def create_project(path):
43 try:
44 abs_path = pathlib.Path(path).resolve()
45 cur_path = pathlib.Path(".").resolve()
46 os.makedirs(os.path.join(path, "build"))
47 os.mkdir(os.path.join(path, "pages"))
48 os.mkdir(os.path.join(path, "templates"))
49 os.mkdir(os.path.join(path, "static"))
50 create_config(path)
51 create_template(path)
52 print(good("Created project directory at %s." % (abs_path)))
53 except FileExistsError:
54 print(bad("Error: specified path exists."))
55
56
57def create_path(path):
58 head, tail = os.path.split(path)
59 now = datetime.datetime.now()
60 today = now.strftime("%Y-%m-%d")
61 url = os.path.splitext(os.path.basename(path))[0]
62
63 try:
64 os.makedirs(os.path.join(PAGES_PATH, head))
65 except FileExistsError:
66 pass
67 if os.path.exists(os.path.join(PAGES_PATH, head, tail)):
68 print(bad("Error: specified path exists."))
69 else:
70 with open(os.path.join(PAGES_PATH, head, tail), "w") as f:
71 to_write = (
72 """---
73template:
74url: {u}
75title:
76subtitle:
77date: {t}
78---\n"""
79 ).format(t=today, u=url)
80 f.write(to_write)
81 print(good("Created %s.") % (os.path.join(PAGES_PATH, head, tail)))
82
83
84def create_config(path):
85 with open(os.path.join(path, "config.py"), "w") as f:
86 f.write(
87 """# config.py - Vite's configuration script
88
89title = ''
90author = ''
91header = ''
92footer = ''
93pre_build = []
94post_build = []
95template = 'index.html' # default is index.html\n"""
96 )
97
98
99def create_template(path):
100 with open(os.path.join(path, "templates", "index.html"), "w") as f:
101 f.write(
102 """<!DOCTYPE html>
103<html>
104<header>
105 {{ header }}
106 <title>
107 {{ title }}
108 </title>
109</header>
110
111<body>
112 {{ body }}
113</body>
114
115<footer>
116 {{ footer }}
117 <p> {{ author }} </p>
118<footer>
119
120 """
121 )
122
123
124# jinja2
125def jinja_render(html, tmpl):
126 template_loader = jinja2.FileSystemLoader("./")
127 env = jinja2.Environment(loader=template_loader)
128 try:
129 template = env.get_template(tmpl)
130 except jinja2.exceptions.TemplateNotFound:
131 template = env.get_template(TEMPL_FILE)
132 meta = html.metadata
133 output = template.render(
134 title=meta["title"] if "title" in meta else config.title,
135 author=meta["author"] if "author" in meta else config.author,
136 header=meta["header"] if "header" in meta else config.header,
137 url=meta["url"] if "url" in meta else "",
138 footer=meta["footer"] if "footer" in meta else config.footer,
139 date=meta["date"] if "date" in meta else "",
140 subtitle=meta["subtitle"] if "subtitle" in meta else "",
141 body=html,
142 )
143 return output
144
145
146def fm_template(metadata):
147 try:
148 page_template = os.path.join(os.path.join(TEMPL_PATH, metadata["template"]))
149 except KeyError:
150 page_template = TEMPL_FILE
151 return page_template
152
153
154def markdown_render(filename):
155 html = markdown_path(
156 os.path.join(PAGES_PATH, filename),
157 extras=[
158 "metadata",
159 "fenced-code-blocks",
160 "header-ids",
161 "footnotes",
162 "smarty-pants",
163 "tables",
164 "link-patterns",
165 ],
166 link_patterns=[
167 (
168 re.compile(
169 r"((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+(:[0-9]+)?|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)"
170 ),
171 r"\1",
172 )
173 ],
174 )
175 return html
176
177
178def html_gen():
179 def index_render(f, d=""):
180 index_html = markdown_render(os.path.join(d, f))
181 output = jinja_render(index_html, fm_template(index_html.metadata))
182 with open(os.path.join(BUILD_PATH, d, "index.html"), "w") as ff:
183 ff.write(output)
184 if d:
185 print(run("Rendered " + white("%s/%s") % (d, f)))
186 else:
187 print(run("Rendered " + white("%s") % (f)))
188
189 def normal_render(f, d=""):
190 html_text = markdown_render(os.path.join(d, f))
191 html_file = os.path.splitext(os.path.join(BUILD_PATH, d, f))[0]
192 os.mkdir(html_file)
193 output = jinja_render(html_text, fm_template(html_text.metadata))
194 with open(os.path.join(html_file, "index.html"), "w") as ff:
195 ff.write(output)
196 if d:
197 print(run("Rendered " + white("%s/%s") % (d, f)))
198 else:
199 print(run("Rendered " + white("%s") % (f)))
200
201 for root, dirs, files in os.walk(PAGES_PATH):
202 for d in dirs:
203 os.mkdir(os.path.join(BUILD_PATH, d))
204 for f in os.listdir(os.path.join(PAGES_PATH, d)):
205 if os.path.splitext(f)[1] != ".md":
206 shutil.copyfile(
207 os.path.join(PAGES_PATH, d, f), os.path.join(BUILD_PATH, d, f)
208 )
209 print(run("Copied " + white("%s/%s") % (d, f)))
210 elif f == "_index.md":
211 index_render(f, d)
212 else:
213 normal_render(f, d)
214
215 for f in os.listdir(PAGES_PATH):
216 if os.path.isfile(os.path.join(PAGES_PATH, f)):
217 if os.path.splitext(f)[1] != ".md":
218 shutil.copyfile(
219 os.path.join(PAGES_PATH, f), os.path.join(BUILD_PATH, f)
220 )
221 print(run("Copied " + white("%s") % (f)))
222 elif f == "_index.md":
223 index_render(f)
224 else:
225 normal_render(f)
226
227
228def server():
229 # handler = http.server.SimpleHTTPRequestHandler
230 # os.chdir(os.path.join(os.getcwd(), BUILD_PATH))
231 server = Server()
232 try:
233 print(
234 run(
235 f'Serving the {italic(yellow("build"))} directory at {white(f"http://localhost:{PORT}")}'
236 )
237 )
238 print(white("Ctrl+C") + " to stop.")
239 server.serve(port=PORT, root="build/")
240 except KeyboardInterrupt:
241 print(info("Stopping server."))
242 sys.exit(1)
243
244
245def clean():
246 for root, dirs, files in os.walk(BUILD_PATH):
247 for f in files:
248 os.unlink(os.path.join(root, f))
249 for d in dirs:
250 shutil.rmtree(os.path.join(root, d))
251
252
253def builder():
254 path = os.getcwd()
255 start = time.process_time()
256 if not os.listdir(os.path.join(path, PAGES_PATH)):
257 print(info(italic("pages") + " directory is empty. Nothing to build."))
258 sys.exit(1)
259 else:
260 try:
261 if config.pre_build != "":
262 print(run("Running pre-build actions..."))
263 for s in config.pre_build:
264 print(info(f"{s}"))
265 call(s)
266 except AttributeError:
267 pass
268 clean()
269 html_gen()
270 if os.path.exists(os.path.join(os.getcwd(), "static")):
271 shutil.copytree(
272 os.path.join(os.getcwd(), "static"), os.path.join(BUILD_PATH, "static")
273 )
274 try:
275 if config.post_build != "":
276 print(run("Running post-build actions..."))
277 for s in config.post_build:
278 print(info(f"{s}"))
279 call([s])
280 except AttributeError:
281 pass
282
283 print(good("Done in %0.5fs." % (time.process_time() - start)))
284