1 | #!/usr/bin/env python
|
2 | import argparse
|
3 | import os
|
4 | import sys
|
5 | import xml.etree.ElementTree as ET
|
6 | from io import StringIO
|
7 | from datetime import datetime
|
8 |
|
9 | import markdown
|
10 | import yaml
|
11 |
|
12 | from bs4 import BeautifulSoup
|
13 | from jinja2 import Environment, FunctionLoader
|
14 |
|
15 |
|
16 | def _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 |
|
73 | def _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 |
|
102 | def 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 |
|
112 | def 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 |
|
144 | def 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 |
|
173 | if __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 | )
|