Pythonrender.py -rwxr-xr-x 6.1 KiB
1#!/usr/bin/env python
2import argparse
3import os
4import sys
5import xml.etree.ElementTree as ET
6from io import StringIO
7from datetime import datetime
8
9import markdown
10import yaml
11
12from bs4 import BeautifulSoup
13from jinja2 import Environment, FunctionLoader
14
15
16def _load_sitemap(path, current_path):
17 by_name = dict()
18 flattened = []
19 with open(path, "r") as fp:
20 _sitemap = yaml.safe_load(fp.read())
21
22 if not _sitemap["enabled"]:
23 return None
24
25 variables = dict()
26 if "variables" in _sitemap:
27 variables = _sitemap["variables"]
28
29 def _filter_entries(entries):
30 entries = list(
31 filter(
32 lambda entry: "enabled" not in entry or entry["enabled"] is True,
33 entries,
34 )
35 )
36 for entry in entries:
37 if "others" in entry:
38 entry["others"] = _filter_entries(entry["others"])
39 return entries
40
41 entries = _filter_entries(_sitemap["entries"])
42
43 def _link(entries, parent=None):
44 for entry in entries:
45 flattened.append(entry)
46 entry["parent"] = parent
47 if "others" in entry:
48 _link(entry["others"], parent=entry)
49
50 def _make_url(link):
51 path = [link["name"]]
52 parent = link["parent"]
53 while parent is not None:
54 path.append(parent["name"])
55 parent = parent["parent"]
56 path.reverse()
57 return "/".join(path)
58
59 _link(entries, parent=None)
60
61 for entry in flattened:
62 target = "/" + _make_url(entry)
63 if target == current_path:
64 entry["active"] = True
65 else:
66 entry["active"] = False
67 entry["url"] = target
68 by_name[target] = entry
69
70 return dict(by_name=by_name, variables=variables, links=entries)
71
72
73def _make_tree(links):
74 def _populate(root, links):
75 for link in links:
76 elm = ET.Element("li")
77 if "directory" in link and link["directory"]:
78 elm.text = link["name"]
79 else:
80 lref = ET.Element("a", href=link["url"])
81 link_text = link["name"]
82 if "date" in link:
83 link_text = link_text + " [" + str(link["date"]) + "]"
84 lref.text = link_text
85 if link["active"]:
86 lref.text = lref.text + " <"
87 elm.append(lref)
88 if "others" in link:
89 others = link["others"]
90 ul = ET.Element("ul")
91 elm.append(ul)
92 _populate(root=ul, links=others)
93 root.append(elm)
94
95 params = {"class": "tree"}
96
97 root = ET.Element("ul", **params)
98 _populate(root=root, links=links)
99 return ET.tostring(root).decode()
100
101
102def load_content(path):
103 with open(path) as fp:
104 text = fp.read()
105 content = markdown.markdown(
106 text,
107 extensions=["fenced_code", "tables"],
108 )
109 return content
110
111
112def generate(template, content_path, stylesheet, link, sitemap, devmode=False):
113 tree = _make_tree(sitemap["links"])
114 with open(template, "r") as fp:
115 template = fp.read()
116 env = Environment(loader=FunctionLoader(lambda name: template))
117 base = env.get_template(template)
118 with open(stylesheet) as fp:
119 style = fp.read()
120 content = load_content(content_path)
121 env_content = Environment(loader=FunctionLoader(lambda name: content))
122 out = base.render(
123 {
124 "content": env_content.get_template("").render(
125 {
126 "sitemap": sitemap,
127 "tree": tree,
128 "variables": sitemap["variables"]
129 }
130 ),
131 "variables": sitemap["variables"],
132 "is_index": False,
133 "link": link,
134 "style": style,
135 "sitemap": sitemap,
136 "tree": tree,
137 "devmode": devmode,
138 "current_time": datetime.now(),
139 }
140 )
141 sys.stdout.write(out)
142
143
144def generate_rss(sitemap):
145 sitemap = _load_sitemap(sitemap, "/")
146
147 rss = ET.Element("rss", attrib={"version": "2.0", "encoding": "UTF-8"})
148 channel = ET.SubElement(rss, "channel")
149
150 title = ET.SubElement(channel, "title")
151 title.text = "Blog of Kevin Schoon"
152 link = ET.SubElement(channel, "link")
153 link.attrib["href"] = "https://kevinschoon.com"
154 description = ET.SubElement(channel, "description")
155 description.text = "Blog of Kevin Schoon"
156
157 for entry in sitemap["by_name"]["/blog"]["others"]:
158 absolute_url = "https://kevinschoon.com/blog/" + entry["name"]
159 item = ET.SubElement(channel, "item")
160 title_item = ET.SubElement(item, "title")
161 link_item = ET.SubElement(item, "link")
162 guid_item = ET.SubElement(item, "guid")
163 guid_item.text = absolute_url
164 description_item = ET.SubElement(item, "description")
165 title_item.text = entry["name"]
166 link_item.attrib["href"] = absolute_url
167 description_item.text = entry["tagline"]
168
169 xml_str = ET.tostring(rss, xml_declaration=True, encoding="utf-8", method="xml")
170 sys.stdout.buffer.write(xml_str)
171
172
173if __name__ == "__main__":
174 parser = argparse.ArgumentParser(description="static renderer")
175 parser.add_argument("-rss", help="if rendering the rss feed", action="store_true")
176 parser.add_argument("-content", help="markdown file with content")
177 parser.add_argument(
178 "-template", default="index.html.jinja", help="jinja template file"
179 )
180 parser.add_argument("-stylesheet", default="./assets/main.css", help="style sheet")
181 parser.add_argument("-sitemap", default="sitemap.yaml", help="sitemap")
182 parser.add_argument("-link", default="/", help="current url")
183 parser.add_argument(
184 "-devmode", action="store_true", help="developer mode (websocket page refresh)"
185 )
186 args = parser.parse_args()
187 sitemap = _load_sitemap(args.sitemap, args.link)
188 if args.rss:
189 generate_rss(args.sitemap)
190 else:
191 generate(
192 template=args.template,
193 content_path=args.content,
194 stylesheet=args.stylesheet,
195 link=args.link,
196 sitemap=sitemap,
197 devmode=args.devmode,
198 )