604 lines
18 KiB
Python
604 lines
18 KiB
Python
![]() |
#!/usr/bin/env python3
|
||
|
|
||
|
import html
|
||
|
import os
|
||
|
import sys
|
||
|
import shutil
|
||
|
|
||
|
html_self_closing_tags = {"!DOCTYPE", "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", "command", "keygen", "menuitem", "frame"}
|
||
|
html_xml_content_tags = {"svg"}
|
||
|
text_url_match = {"/", "http://", "https://"}
|
||
|
|
||
|
traversed_links = set()
|
||
|
config_values = {
|
||
|
"logo": "",
|
||
|
"logo_url": "/",
|
||
|
"blank": False,
|
||
|
"icon": "/favicon.ico",
|
||
|
"stylesheet": "/stylesheet.css",
|
||
|
"title_format": "%t",
|
||
|
"logo_width": "192",
|
||
|
"logo_height": "32"
|
||
|
}
|
||
|
|
||
|
default_theme = {
|
||
|
"background-color": "#f0f0f0",
|
||
|
"text-color": "#7f687f",
|
||
|
"link-color": "#af8faf",
|
||
|
"header-color": "#e0e0e0",
|
||
|
"footer-color": "#e0e0e0",
|
||
|
"top-line-color": "#c0c0c0",
|
||
|
"bottom-line-color": "#c0c0c0",
|
||
|
"container-color": "#f0f0f0",
|
||
|
"content-box-color": "#e8e8e8",
|
||
|
"content-border-tl-color": "#f8f8f8",
|
||
|
"content-border-br-color": "#c8c8c8",
|
||
|
"container-padding": 80,
|
||
|
"content-min-height": 36,
|
||
|
"content-margin": 5,
|
||
|
"content-line-pad": 5,
|
||
|
"content-side-pad": 10,
|
||
|
"header-height": 40,
|
||
|
"header-padding-left": 10,
|
||
|
"header-padding-right": 10,
|
||
|
"header-gap": 5,
|
||
|
"header-elem-size": 32,
|
||
|
"header-line-pad": 4,
|
||
|
"header-side-pad": 10,
|
||
|
"footer-height": 32,
|
||
|
"footer-padding-left": 15,
|
||
|
"footer-padding-right": 15,
|
||
|
"footer-gap": 8,
|
||
|
"footer-elem-size": 20,
|
||
|
"footer-line-pad": 2,
|
||
|
"footer-side-pad": 7,
|
||
|
"line-height": 20,
|
||
|
"text-size": 14,
|
||
|
"header-text-size": 14,
|
||
|
"footer-text-size": 14
|
||
|
}
|
||
|
|
||
|
base_stylesheet = [
|
||
|
("a", {
|
||
|
"color": "[#link-color]",
|
||
|
"text-decoration": "none"
|
||
|
}),
|
||
|
("body", {
|
||
|
"box-sizing": "border-box",
|
||
|
"line-height": "[#line-height]px",
|
||
|
"display": "flex",
|
||
|
"flex-direction": "column",
|
||
|
"background-color": "[#background-color]",
|
||
|
"margin": "0",
|
||
|
"color": "[#text-color]",
|
||
|
"font-size": "[#text-size]px"
|
||
|
}),
|
||
|
(".flex-container", {
|
||
|
"box-sizing": "border-box",
|
||
|
"display": "flex",
|
||
|
"justify-content": "space-between",
|
||
|
"margin": "0"
|
||
|
}),
|
||
|
("nav.flex-container", {
|
||
|
"background-color": "[#header-color]",
|
||
|
"border-style": "solid",
|
||
|
"border-width": "0 0 1px 0",
|
||
|
"border-color": "[#top-line-color]"
|
||
|
}),
|
||
|
("footer.flex-container", {
|
||
|
"background-color": "[#footer-color]",
|
||
|
"border-style": "solid",
|
||
|
"border-width": "1px 0 0 0",
|
||
|
"border-color": "[#bottom-line-color]"
|
||
|
}),
|
||
|
(".full-container", {
|
||
|
"box-sizing": "border-box",
|
||
|
"padding": "0 0 [#container-padding]px 0",
|
||
|
"flex-grow": "1",
|
||
|
"background-color": "[#container-color]",
|
||
|
"margin": "0"
|
||
|
}),
|
||
|
(".base-container", {
|
||
|
"min-height": "[#content-min-height]px",
|
||
|
"align-items": "center",
|
||
|
"box-sizing": "border-box",
|
||
|
"padding": "[#content-line-pad]px [#content-side-pad]px",
|
||
|
"background-color": "[#content-box-color]",
|
||
|
"margin": "[#content-margin]px",
|
||
|
"border-style": "solid",
|
||
|
"border-width": "1px",
|
||
|
"border-color": "[#content-border-tl-color] [#content-border-br-color] [#content-border-br-color] [#content-border-tl-color]"
|
||
|
}),
|
||
|
(".list-container", {
|
||
|
"column-gap": "[#header-gap]px",
|
||
|
"display": "flex",
|
||
|
"min-height": "[#header-height]px",
|
||
|
"row-gap": "[#header-gap]px",
|
||
|
"align-items": "center",
|
||
|
"box-sizing": "border-box",
|
||
|
"padding": "0 [#header-padding-right]px 0 [#header-padding-left]px",
|
||
|
"margin": "0"
|
||
|
}),
|
||
|
(".compact-container", {
|
||
|
"column-gap": "[#footer-gap]px",
|
||
|
"display": "flex",
|
||
|
"min-height": "[#footer-height]px",
|
||
|
"row-gap": "[#footer-gap]px",
|
||
|
"align-items": "center",
|
||
|
"box-sizing": "border-box",
|
||
|
"padding": "0 [#footer-padding-right]px 0 [#footer-padding-left]px",
|
||
|
"margin": "0"
|
||
|
}),
|
||
|
(".list-item", {
|
||
|
"align-items": "center",
|
||
|
"align-self": "center",
|
||
|
"box-sizing": "border-box",
|
||
|
"display": "flex",
|
||
|
"flex-basis": "auto",
|
||
|
"flex-grow": "0",
|
||
|
"flex-shrink": "0",
|
||
|
"min-height": "[#header-elem-size]px",
|
||
|
"min-width": "[#header-elem-size]px",
|
||
|
"padding": "[#header-line-pad]px [#header-side-pad]px",
|
||
|
"margin": "0",
|
||
|
"flex-wrap": "wrap",
|
||
|
"font-size": "[#header-text-size]px"
|
||
|
}),
|
||
|
(".compact-item", {
|
||
|
"align-items": "center",
|
||
|
"align-self": "center",
|
||
|
"box-sizing": "border-box",
|
||
|
"display": "flex",
|
||
|
"flex-basis": "auto",
|
||
|
"flex-grow": "0",
|
||
|
"flex-shrink": "0",
|
||
|
"min-height": "[#footer-elem-size]px",
|
||
|
"min-width": "[#footer-elem-size]px",
|
||
|
"padding": "[#footer-line-pad]px [#footer-side-pad]px",
|
||
|
"margin": "0",
|
||
|
"flex-wrap": "wrap",
|
||
|
"font-size": "[#footer-text-size]px"
|
||
|
}),
|
||
|
("a.nocolor", {
|
||
|
"color": "inherit"
|
||
|
})
|
||
|
]
|
||
|
|
||
|
def tree2html(tree, indent=0):
|
||
|
lines = []
|
||
|
for node in tree:
|
||
|
if type(node) is not tuple or len(node) < 1 or len(node) > 3 or type(node[0]) is not str or (len(node) == 2 and type(node[1]) not in {dict, list}) or (len(node) == 3 and (type(node[1]) is not dict or type(node[2]) is not list)):
|
||
|
raise Exception("element ist not a valid html tuple: (tag), (tag, properties), (tag, children) or (tag, properties, children), got " + str(type(node)) + " (" + str(node) + ")")
|
||
|
props = ""
|
||
|
inner = ""
|
||
|
if len(node) > 1 and type(node[1]) is dict:
|
||
|
for prop, value in node[1].items():
|
||
|
props += ' ' + html.escape(prop) + (('="' + html.escape(value) + '"') if value is not None else "")
|
||
|
if len(node) == 3 or (len(node) == 2 and not props):
|
||
|
if node[0] in html_self_closing_tags:
|
||
|
raise Exception("self closing tag <" + node[0] + "> cannot have children")
|
||
|
lines.append((indent * "\t") + "<" + node[0] + props + ">")
|
||
|
for child in node[2 if len(node) == 3 else 1]:
|
||
|
if type(child) is list:
|
||
|
lines.extend(tree2html(child, indent + 1))
|
||
|
elif type(child) is tuple:
|
||
|
lines.extend(tree2html([child], indent + 1))
|
||
|
elif type(child) is str:
|
||
|
lines.append(((indent + 1) * "\t") + (child if node[0] in html_xml_content_tags else html.escape(child)))
|
||
|
else:
|
||
|
raise Exception("element must be tuple, list or str, got " + str(type(child)) + " (" + str(child) + ")")
|
||
|
lines.append((indent * "\t") + "</" + node[0] + ">")
|
||
|
else:
|
||
|
lines.append((indent * "\t") + "<" + node[0] + props + ">" + (("</" + node[0] + ">") if node[0] not in html_self_closing_tags else ""))
|
||
|
return lines
|
||
|
|
||
|
def get_key_value(text, separator=" "):
|
||
|
space = text.find(separator)
|
||
|
if space < 0:
|
||
|
return (text, None)
|
||
|
else:
|
||
|
return (text[:space], text[space + 1:])
|
||
|
|
||
|
def get_html_tags(line, variables):
|
||
|
if not line:
|
||
|
return None
|
||
|
elif line.startswith("###"):
|
||
|
return [("h1", text2tree(line[3:]))]
|
||
|
elif line.startswith("##"):
|
||
|
return [("h2", text2tree(line[2:]))]
|
||
|
elif line.startswith("#"):
|
||
|
return [("h3", text2tree(line[1:]))]
|
||
|
elif line.startswith("[#") and line.endswith("]"):
|
||
|
key, value = get_key_value(line[2:-1])
|
||
|
match key:
|
||
|
case "svg":
|
||
|
svg = value.split(" ", 6)
|
||
|
return [("svg", {"width": svg[0], "height": svg[1], "viewBox": svg[2] + " " + svg[3] + " " + svg[4] + " " + svg[5]}, [svg[6]])]
|
||
|
case "img":
|
||
|
img = value.split(" ", 2)
|
||
|
src, alt = get_key_value(img[0], ":")
|
||
|
props = {"src": src}
|
||
|
if src.startswith("/"):
|
||
|
global traversed_links
|
||
|
traversed_links.add(src)
|
||
|
if alt is not None:
|
||
|
props["alt"] = alt
|
||
|
if len(img) >= 2 and (img[1].startswith("w:") or img[1].startswith("h:")):
|
||
|
props["width" if img[1].startswith("w:") else "height"] = img[1][2:]
|
||
|
elif len(img) >= 3:
|
||
|
props["width"] = img[1]
|
||
|
props["height"] = img[2]
|
||
|
return [("a", {"href": src}, [("img", props)])]
|
||
|
if key in variables.keys():
|
||
|
return variables[key]
|
||
|
return None
|
||
|
|
||
|
def text2tree(line, nocolor=False):
|
||
|
blank = config_values["blank"]
|
||
|
tree = []
|
||
|
tpos = pos = 0
|
||
|
while True:
|
||
|
lpos = line.find("[", pos)
|
||
|
if lpos < 0:
|
||
|
break
|
||
|
epos = line.find("]", lpos + 1)
|
||
|
if epos < 0:
|
||
|
break
|
||
|
sub = line[lpos + 1:epos]
|
||
|
for prefix in text_url_match:
|
||
|
if sub.startswith(prefix):
|
||
|
url, alt = get_key_value(sub)
|
||
|
props = {"href": url}
|
||
|
if nocolor:
|
||
|
props["class"] = "nocolor"
|
||
|
if prefix == "/":
|
||
|
global traversed_links
|
||
|
traversed_links.add(url)
|
||
|
if blank:
|
||
|
props["target"] = "_blank"
|
||
|
if tpos < lpos:
|
||
|
tree.append(line[tpos:lpos])
|
||
|
tree.append(("a", props, [alt if alt else url]))
|
||
|
tpos = epos + 1;
|
||
|
break
|
||
|
pos = epos + 1;
|
||
|
if tpos < len(line):
|
||
|
tree.append(line[tpos:])
|
||
|
return tree
|
||
|
|
||
|
def lines2tree(text, variables):
|
||
|
tree = []
|
||
|
paragraph = []
|
||
|
for line in text:
|
||
|
tags = get_html_tags(line, variables)
|
||
|
if tags:
|
||
|
if paragraph:
|
||
|
tree.append(("p", paragraph))
|
||
|
paragraph = []
|
||
|
tree.extend(tags)
|
||
|
continue
|
||
|
if paragraph:
|
||
|
paragraph.append(("br",))
|
||
|
paragraph.extend(text2tree(line))
|
||
|
if paragraph:
|
||
|
tree.append(("p", paragraph))
|
||
|
return tree
|
||
|
|
||
|
def line2tree(line, variables, nocolor):
|
||
|
tags = get_html_tags(line, variables)
|
||
|
if tags:
|
||
|
return tags
|
||
|
return text2tree(line, nocolor)
|
||
|
|
||
|
def filter_text(data):
|
||
|
for elem in data:
|
||
|
if type(elem) is tuple and elem[0] in {"svg", "img"}:
|
||
|
return [("span", [elem]) if type(elem) is str else elem for elem in data]
|
||
|
return [("span", data)]
|
||
|
|
||
|
def filter_entry(entry, clazz):
|
||
|
elem = entry if type(entry) is not list or len(entry) == 0 else entry[0]
|
||
|
if type(elem) == tuple and len(elem) >= 2 and type(elem[1]) == dict and "class" in elem[1].keys() and clazz + "-item" in elem[1]["class"].split(" "):
|
||
|
return entry if type(entry) == list else [entry]
|
||
|
else:
|
||
|
return [("p", {"class": clazz + "-item"}, filter_text(entry))]
|
||
|
|
||
|
def make_element(elems, variables, clazz):
|
||
|
tree = []
|
||
|
arr = None
|
||
|
for elem in elems:
|
||
|
if type(elem) is list:
|
||
|
data = elem
|
||
|
elif type(elem) is tuple:
|
||
|
data = [elem]
|
||
|
elif type(elem) is str:
|
||
|
if elem == "[":
|
||
|
if arr is not None:
|
||
|
data = arr
|
||
|
arr = []
|
||
|
else:
|
||
|
arr = []
|
||
|
continue
|
||
|
elif elem == "]":
|
||
|
if arr is None:
|
||
|
continue
|
||
|
data = arr
|
||
|
arr = None
|
||
|
else:
|
||
|
data = line2tree(elem, variables, clazz == "list")
|
||
|
if arr is not None:
|
||
|
arr.extend(data)
|
||
|
continue
|
||
|
else:
|
||
|
data = str(elem)
|
||
|
tree.extend(filter_entry(data, clazz))
|
||
|
if arr is not None:
|
||
|
tree.extend(filter_entry(arr, clazz))
|
||
|
return ("div", {"class": clazz + "-container"}, tree)
|
||
|
|
||
|
def make_head(title):
|
||
|
return [
|
||
|
("meta", {"http-equiv": "content-type", "content": "text/html; charset=UTF-8"}),
|
||
|
("title", [config_values["title_format"].replace("%t", title)]),
|
||
|
("link", {"rel": "icon", "type": "image/x-icon", "href": config_values["icon"]}),
|
||
|
("link", {"rel": "stylesheet", "href": config_values["stylesheet"]})
|
||
|
]
|
||
|
|
||
|
def make_links(pages, clazz):
|
||
|
logo = config_values["logo"]
|
||
|
logo_url = config_values["logo_url"]
|
||
|
logo_width = config_values["logo_width"]
|
||
|
logo_height = config_values["logo_height"]
|
||
|
nav = []
|
||
|
if logo:
|
||
|
alt = "Logo"
|
||
|
for page in pages:
|
||
|
if page["url"] is logo_url:
|
||
|
if page["name"]:
|
||
|
alt = page["name"]
|
||
|
break
|
||
|
nav.append(("a", {"href": logo_url, "class": clazz + "-item nocolor"}, [("img", {"width": logo_width, "height": logo_height, "src": logo, "alt": "Logo"})]))
|
||
|
nav.extend([("a", {"href": page["url"], "class": clazz + "-item nocolor"}, [page["name"]]) for page in pages if page["name"] and (not logo or page["url"] is not logo_url)])
|
||
|
return nav
|
||
|
|
||
|
def make_bar(left, center, right, pages, variables, clazz, tag):
|
||
|
variables = variables.copy()
|
||
|
variables["nav"] = make_links(pages, clazz)
|
||
|
bar = [make_element(left, variables, clazz), make_element(right, variables, clazz)]
|
||
|
if center:
|
||
|
bar = [bar[0], make_element(center, variables, clazz), bar[1]]
|
||
|
return (tag, {"class": "flex-container"}, bar)
|
||
|
|
||
|
def make_content(sections, variables):
|
||
|
tree = []
|
||
|
for section in sections:
|
||
|
tree.append(("div", {"class": "base-container"}, lines2tree(section, variables)))
|
||
|
return ("div", {"class": "full-container"}, tree)
|
||
|
|
||
|
def compile_page(page, header, footer, variables):
|
||
|
return [("!DOCTYPE", {"html": None}), ("html", [("head", make_head(page["title"])), ("body", [header, make_content(page["sections"], variables), footer])])]
|
||
|
|
||
|
def sort_pages(page):
|
||
|
return page["sort"]
|
||
|
|
||
|
def compile_pages(pages):
|
||
|
pages.sort(key=sort_pages)
|
||
|
variables = {
|
||
|
}
|
||
|
header = make_bar(read_list("header/left", ["[#nav]"]), read_list("header/center"), read_list("header/right"), pages, variables, "list", "nav")
|
||
|
footer = make_bar(read_list("footer/left"), read_list("footer/center"), read_list("footer/right"), pages, variables, "compact", "footer")
|
||
|
return [(page["file"], compile_page(page, header, footer, variables)) for page in pages]
|
||
|
|
||
|
def read_page(filename):
|
||
|
path = os.path.join(config_values["indir"], filename + ".sml")
|
||
|
print("Reading " + path)
|
||
|
with open(path, "r") as fd:
|
||
|
data = fd.read().splitlines()
|
||
|
page = {
|
||
|
"name": None,
|
||
|
"title": filename,
|
||
|
"sort": 0
|
||
|
}
|
||
|
sections = []
|
||
|
subpage = []
|
||
|
for line in data:
|
||
|
if line.startswith("[#") and line.endswith("]"):
|
||
|
key, value = get_key_value(line[2:-1])
|
||
|
match key:
|
||
|
case "name":
|
||
|
page["name"] = value
|
||
|
case "title":
|
||
|
page["title"] = value
|
||
|
case "sort":
|
||
|
page["sort"] = int(value)
|
||
|
case "index":
|
||
|
if os.path.basename(filename) != "index":
|
||
|
filename = os.path.join(filename, "index")
|
||
|
case "":
|
||
|
sections.append(subpage)
|
||
|
subpage = []
|
||
|
case _:
|
||
|
subpage.append(line)
|
||
|
else:
|
||
|
subpage.append(line)
|
||
|
if subpage:
|
||
|
sections.append(subpage)
|
||
|
page["sections"] = sections
|
||
|
page["url"] = get_file_url(filename)
|
||
|
page["file"] = filename
|
||
|
return page
|
||
|
|
||
|
def read_list(filename, default=[], directory=None):
|
||
|
path = os.path.join(config_values["indir"] if directory is None else directory, filename + ".sml")
|
||
|
if not os.path.exists(path):
|
||
|
if default is not None:
|
||
|
print("Writing default " + path)
|
||
|
base = os.path.dirname(path)
|
||
|
if not os.path.exists(base):
|
||
|
os.makedirs(base)
|
||
|
with open(path, "w") as fd:
|
||
|
fd.write('\n'.join(default))
|
||
|
return default
|
||
|
return []
|
||
|
print("Reading " + path)
|
||
|
with open(path, "r") as fd:
|
||
|
data = fd.read().splitlines()
|
||
|
return data
|
||
|
|
||
|
def write_page(filename, data, extension="html"):
|
||
|
filename += (("." + extension) if extension else "")
|
||
|
path = os.path.join(config_values["outdir"], filename)
|
||
|
print("Writing " + path)
|
||
|
base = os.path.dirname(path)
|
||
|
if not os.path.exists(base):
|
||
|
os.makedirs(base)
|
||
|
with open(path, "w") as fd:
|
||
|
fd.write('\n'.join(data))
|
||
|
return filename
|
||
|
|
||
|
def read_pages():
|
||
|
directory = config_values["indir"]
|
||
|
pages = []
|
||
|
for root, dirs, files in os.walk(directory):
|
||
|
for filename in files:
|
||
|
if not filename.endswith(".sml"):
|
||
|
continue
|
||
|
path = os.path.join(os.path.relpath(root, directory), filename)
|
||
|
path = path[2:-4] if path.split(os.sep)[0] == "." else path[:-4]
|
||
|
if os.path.dirname(path) not in {"header", "footer"} and path != "config" and path != "theme":
|
||
|
pages.append(read_page(path))
|
||
|
return pages
|
||
|
|
||
|
def copy_file(filename):
|
||
|
ipath = os.path.join(config_values["indir"], filename)
|
||
|
opath = os.path.join(config_values["outdir"], filename)
|
||
|
if not os.path.exists(ipath):
|
||
|
print("Warning: file " + ipath + " does not exist")
|
||
|
return
|
||
|
print("Copying " + ipath + " to " + opath)
|
||
|
base = os.path.dirname(opath)
|
||
|
if not os.path.exists(base):
|
||
|
os.makedirs(base)
|
||
|
shutil.copy(ipath, opath)
|
||
|
|
||
|
def read_config(directory, output):
|
||
|
global config_values
|
||
|
config = read_list("config", [" [#" + (prop if type(value) is bool else prop + " " + str(value)) + "]" for prop, value in config_values.items()], directory)
|
||
|
for cfg in config:
|
||
|
if cfg.startswith("[#") and cfg.endswith("]"):
|
||
|
prop, value = get_key_value(cfg[2:-1])
|
||
|
if prop not in config_values.keys():
|
||
|
print("Warning: config value '" + prop + "' is unknown")
|
||
|
continue
|
||
|
ovalue = config_values[prop]
|
||
|
if value is None:
|
||
|
if type(ovalue) is not bool:
|
||
|
print("Warning: config value '" + prop + "' needs an argument")
|
||
|
continue
|
||
|
value = True
|
||
|
elif type(ovalue) is bool:
|
||
|
if value.lower() != "true" and value.lower() != "false":
|
||
|
print("Warning: unknown bool value for config value '" + prop + "'")
|
||
|
continue
|
||
|
value = value.lower() == "true"
|
||
|
print("Warning: redundant bool value for config value '" + prop + "'")
|
||
|
config_values[prop] = value
|
||
|
config_values["indir"] = directory
|
||
|
config_values["outdir"] = output
|
||
|
for prop, value in config_values.items():
|
||
|
print(prop + " = " + str(value))
|
||
|
|
||
|
def add_default_links():
|
||
|
global traversed_links
|
||
|
for key in "logo", "icon", "stylesheet":
|
||
|
if config_values[key].startswith("/"):
|
||
|
traversed_links.add(config_values[key])
|
||
|
|
||
|
def read_theme():
|
||
|
theme = read_list("theme", ["[#" + prop + " " + str(value) + "]" for prop, value in default_theme.items()])
|
||
|
style = {prop: str(value) for prop, value in default_theme.items()}
|
||
|
for line in theme:
|
||
|
if line.startswith("[#") and line.endswith("]"):
|
||
|
prop, value = get_key_value(line[2:-1])
|
||
|
if prop not in default_theme.keys():
|
||
|
print("Warning: theme property '" + prop + "' is unknown")
|
||
|
continue
|
||
|
if value is None:
|
||
|
continue
|
||
|
if type(default_theme[prop]) is int:
|
||
|
test = int(value)
|
||
|
style[prop] = value
|
||
|
return style
|
||
|
|
||
|
def compile_stylesheet(classes, variables):
|
||
|
lines = []
|
||
|
for clazz, props in classes:
|
||
|
lines.append(clazz + " {")
|
||
|
for prop, value in props.items():
|
||
|
plain = ""
|
||
|
tpos = pos = 0
|
||
|
while True:
|
||
|
lpos = value.find("[#", pos)
|
||
|
if lpos < 0:
|
||
|
break
|
||
|
epos = value.find("]", lpos + 2)
|
||
|
if epos < 0:
|
||
|
break
|
||
|
key, _ = get_key_value(value[lpos + 2:epos])
|
||
|
if key in variables.keys():
|
||
|
if tpos < lpos:
|
||
|
plain += value[tpos:lpos]
|
||
|
plain += variables[key]
|
||
|
tpos = epos + 1;
|
||
|
pos = epos + 1;
|
||
|
if tpos < len(value):
|
||
|
plain += value[tpos:]
|
||
|
lines.append("\t" + prop + ": " + plain + ";")
|
||
|
lines.append("}")
|
||
|
return lines
|
||
|
|
||
|
def write_stylesheet(style):
|
||
|
return write_page(config_values["stylesheet"][1:], style, extension=None)
|
||
|
|
||
|
def write_pages(pages):
|
||
|
written = set()
|
||
|
for path, tree in pages:
|
||
|
written.add(write_page(path, tree2html(tree)))
|
||
|
return written
|
||
|
|
||
|
def get_url_file(url):
|
||
|
url = url[1:]
|
||
|
return url if "." in url else ("index.html" if not url else os.path.join(url[:-1] if url.endswith("/") else url, "index.html"))
|
||
|
|
||
|
def get_file_url(filename):
|
||
|
return "/" + (("" if filename == "index" else os.path.dirname(filename)) if os.path.basename(filename) == "index" else (filename + ".html"))
|
||
|
|
||
|
def copy_linked_files(written):
|
||
|
for url in traversed_links:
|
||
|
filename = get_url_file(url)
|
||
|
if filename not in written:
|
||
|
copy_file(filename)
|
||
|
|
||
|
def clean_directory():
|
||
|
output = config_values["outdir"]
|
||
|
if os.path.exists(output):
|
||
|
shutil.rmtree(output)
|
||
|
|
||
|
def main():
|
||
|
if len(sys.argv) < 2:
|
||
|
print(sys.argv[0] + " <indir> <outdir>")
|
||
|
return
|
||
|
read_config(sys.argv[1], sys.argv[2])
|
||
|
add_default_links()
|
||
|
pages = read_pages()
|
||
|
theme = read_theme()
|
||
|
pages = compile_pages(pages)
|
||
|
style = compile_stylesheet(base_stylesheet, theme)
|
||
|
clean_directory()
|
||
|
written = write_pages(pages)
|
||
|
written.add(write_stylesheet(style))
|
||
|
copy_linked_files(written)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|