Skip to content
Snippets Groups Projects
Verified Commit 5a36148a authored by Jake's avatar Jake :speech_balloon:
Browse files

added fgs

parent b7a8e0e2
No related branches found
No related tags found
No related merge requests found
.PHONY: run
run:
python3 __main__.py
#!/usr/bin/env python3
import json
import os
import sys
import reader
import generator
import writer
CONTENT_DIR = '../content'
OUTPUT_DIR = '../public'
THEME_DIR = '../theme'
def main():
print("Hello World")
config = {}
with open('../config.json') as f:
config.update(json.loads(f.read()))
with open('../lang.json') as f:
config['lang'] = json.loads(f.read())
with open(CONTENT_DIR + '/config.json') as f:
config.update(json.loads(f.read()))
print(config)
mdreader = reader.MarkdownReader(config)
pages = []
directory = os.path.join(CONTENT_DIR, ".")
parse_dir(directory, pages, mdreader)
static_dirs = []
for sdir in config['static_directories']:
if "." in sdir:
raise Exception("Illegal static directory name: ", sdir)
static_dirs.append(CONTENT_DIR + "/"+ sdir)
static_files = {}
for sdir in static_dirs:
read_static_dir(sdir, static_files)
read_static_dir(THEME_DIR + '/static', static_files, [config['theme']['static_dir']])
context = {}
gen = generator.Generator(config, context)
gen.generate_context(pages, static_files)
wrt = writer.Writer(config, context, OUTPUT_DIR, THEME_DIR)
gen.generate_output(wrt)
def read_static_dir(directory, static_files, subpath = []):
print("static_dir: " + directory);
for filename in os.listdir(directory):
fp = os.path.join(directory, filename)
if os.path.isfile(fp) and not filename.startswith("."):
lpath = '/'.join(subpath + [filename])
with open(fp, "rb") as f:
static_files[lpath] = f.read()
print("read: ", fp, " as: ", lpath)
elif os.path.isdir(fp) and not filename.startswith("."):
read_static_dir(fp, static_files, subpath + [filename])
def parse_dir(directory, pages, mdreader, subpath = []):
print("parse_dir: " + directory);
for filename in os.listdir(directory):
f = os.path.join(directory, filename)
if os.path.isfile(f) and filename.endswith(".md"):
pages.append(mdreader.read_and_parse_file(f, subpath))
elif os.path.isdir(f) and not filename.startswith("."):
parse_dir(f, pages, mdreader, subpath + [filename])
if __name__ == '__main__':
main()
class Generator:
def __init__(self, config, context):
self.config = config
self.context = context
def generate_context(self, pages, static_files):
# static_files
self.context['static_files'] = static_files
published_pages = []
for page in pages:
if page.status == "published":
published_pages.append(page)
# pages
all_pages = {}
for page in published_pages:
if page.lang not in all_pages:
all_pages[page.lang] = {}
if page.slug in all_pages[page.lang]:
raise Exception("duplicate language (",lang,") for slug '", slug ,"'")
all_pages[page.lang][page.slug] = page
self.context['pages'] = all_pages
# pages_modified
pages_modified = {}
for lang in self.config['lang']['supported']:
lang_pages = []
for page in published_pages:
if page.lang == lang:
lang_pages.append(page)
lang_pages.sort()
pages_modified[lang] = lang_pages
self.context['pages_modified'] = pages_modified
# TODO hidden pages
# TODO draft pages
# TODO authors
# categories
categories = {}
for page in published_pages:
if page.category not in categories:
categories[page.category] = {}
if page.lang not in categories[page.category]:
categories[page.category][page.lang] = []
categories[page.category][page.lang].append(page)
self.context['categories'] = categories
# tags
tags = {}
for page in published_pages:
for tag in page.tags:
if tag not in tags:
tags[tag] = {}
if page.lang not in tags[tag]:
tags[tag][page.lang] = []
tags[tag][page.lang].append(page)
self.context['tags'] = tags
def generate_homepage(self, writer, lang, path):
page = self.context['pages'][lang][self.config['theme']['homepage_slug']]
writer.write_template(page.template, path , lang, {'page': page})
def generate_output(self, writer):
for sf, raw in self.context['static_files'].items():
print ("writing binary file: ", sf)
writer.write_file(sf, raw, mode="wb")
for lang in self.config['lang']['supported']:
# all pages
for page in self.context['pages'][lang].values():
writer.write_template(page.template, page.url, lang, {'page': page})
# homepages for languages
self.generate_homepage(writer, lang, lang + "/index.html")
# homepage
self.generate_homepage(writer, self.config['lang']['default'], "index.html")
class Page:
#filename = None
#subpath = None
#raw = None
#metadata = None
#content = None
#toc = None
#title = None
#category = None
#slug = None
#lang = None
#date_created = None
#date_modified = None
#status = None
#authors = None
#tags = None
#template = None
def __init__(self, filename, subpath, raw, metadata, content, toc, title, category, slug, lang, date_created, date_modified, authors, last_modification_author, status, config):
self.filename = filename
self.subpath = subpath
self.raw = raw
self.metadata = metadata
self.content = content
self.toc = toc
self.title = title
self.category = category
self.slug = slug
self.lang = lang
self.date_created = Date(date_created, config)
self.date_modified = Date(date_modified, config)
self.status = status
#self.config = config
# authors
self.authors = []
for local_part, domain, name in authors:
self.authors.append(Author(local_part, domain, name, config))
if last_modification_author:
self.last_modification_author = Author(last_modification_author[0], last_modification_author[1], last_modification_author[2], config)
else:
self.last_modification_author = None
#print(authors)
#print(last_modification_author)
# tags
self.tags = []
if 'tags' in metadata:
for t in metadata['tags']:
if (t != category):
self.tags.append(t)
self.tags.append(self.category) # the category is also a default tag
# template
if 'template' in metadata:
self.template = metadata['template']
else:
self.template = config['theme']['default_template']
# url
self.url = self.lang + '/' + self.category + '/' + self.slug + ".html"
def __lt__(self, other):
return self.date_modified < other.date_modified
class Date:
def __init__(self, dt, config):
self.dt = dt
#self.config = config
# TODO various formats
def isoformat(self):
return self.dt.isoformat()
def __lt__(self, other):
return self.dt.timestamp() < other.dt.timestamp()
class Author:
def __init__(self, email_local_part, email_domain, name, config):
self.email_local_part = email_local_part
self.email_domain = email_domain
self.email = email_local_part + '@' + email_domain
self.name = name
#self.config = config
# TODO md5 hash etc.
$table-of-contents$
import page
import frontmatter
from datetime import datetime
from dateutil import parser as dtparser
import subprocess
import os
class MarkdownReader:
def __init__(self, config):
self.config = config
def read_and_parse_file(self, path, subpath):
if not path.endswith(".md"):
raise Exception("can only parse markdown files: ", path)
elif len(subpath) > 1:
raise Exception("markdown file is too deep in directory structure: ", path, subpath)
print("parsing file: ", path, subpath)
f = open(path)
rawcontent = f.read()
f.close()
metadata, _ = frontmatter.parse(rawcontent)
#print(metadata)
category_name = self.get_category_name(metadata, subpath)
# content
content = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5")
#print(content)
# TOC
toc = self.run_pandoc(rawcontent, self.config['pandoc']['base'], self.config['pandoc']['extensions'], "html5", ["--template", "./pandoc_toc.html", "--toc", "--toc-depth", str(self.config['toc_depth'])])
#print((toc))
# title
if 'title' not in metadata:
raise Exception("File is missing title in metadata: ", path, subpath)
title = metadata['title']
# slug and lang
pathlist = path.split('/')
filename = pathlist[-1]
filenamelist = filename.split('.')
filenamelist = filenamelist[:-1] # remove .md
slug = None
lang = None
if len(filenamelist) < 1:
raise Exception("filename is empty?", path, subpath)
elif len(filenamelist) == 1:
slug = filenamelist[0]
elif len(filenamelist) == 2:
slug = filenamelist[0]
lang = filenamelist[1]
if 'slug' in metadata:
slug = metadata['slug']
if 'lang' in metadata:
lang = metadata['lang']
if lang == None:
lang = self.config['lang']['default']
if not self.is_supported_lang(lang):
raise Exception("language is not supported: ", lang)
slug = self.secure_slug(slug)
#print("slug: ", slug)
#print("lang: ", lang)
# date_created and date_modified
date_modified = datetime.now()
date_created = datetime.now()
date_changes = self.run_git(path, "log", ["--follow", "--format=%ad", "--date", "iso-strict"]).splitlines()
#print("date_changes: ", date_changes)
if (len(date_changes) > 0):
date_modified = datetime.fromisoformat(date_changes[0])
date_created = datetime.fromisoformat(date_changes[-1])
if 'date' in metadata:
date_created = dtparser.parse(metadata['date'])
if 'modified' in metadata:
date_modified = dtparser.parse(metadata['modified'])
#print("created: ", date_created)
#print("last changed: ", date_modified)
# author
# TODO author from metadata
authors_raw = self.run_git(path, "log", ["--follow", "--format=%aE@%aN", "--use-mailmap"]).splitlines()
authors = []
known_author_raws = []
for author_raw in authors_raw:
if author_raw not in known_author_raws:
authors.append(self.extract_author(author_raw))
known_author_raws.append(author_raw)
if len(authors_raw) > 0:
last_modification_author = self.extract_author(authors_raw[0])
else:
last_modification_author = None
# status
status = self.config['default_status']
if 'status' in metadata:
status = metadata['status']
valid_status = ["published", "draft", "hidden"]
if status not in valid_status:
raise Exception("invalid status '", status, "' must be one of ", valid_status)
# TODO summary
p = page.Page(
filename,
subpath,
rawcontent,
metadata,
content,
toc,
title,
category_name,
slug,
lang,
date_created,
date_modified,
authors,
last_modification_author,
status,
self.config)
return p
def extract_author(self, raw):
author_split = raw.split('@')
author_local_part = author_split[0]
author_domain = author_split[1]
author_name = '@'.join(author_split[2:])
return (author_local_part, author_domain, author_name)
def is_supported_lang(self, lang):
if not isinstance(lang, str):
return False
return (lang in self.config['lang']['supported'])
def secure_slug(self, slug):
if not isinstance(slug, str):
raise Exception("slug is not a string: '", slug, "'")
slug = slug.lower()
whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
res = ""
for c in slug:
if c in whitelist:
#print ("c: '", c,"'")
res += c
#print("res: '", res, "'")
if len(res) == 0:
raise Exception("slug is empty")
return res
def get_category_name(self, metadata, subpath):
if 'category' in metadata:
return metadata['category']
elif len(subpath) == 1:
return subpath[0]
else:
return 'misc'
def run_git(self, path, subcmd, extra_args):
real_path = os.path.realpath(path)
filename = os.path.basename(real_path)
dir_path = os.path.dirname(real_path)
git_bin = "git"
args = [git_bin, subcmd] + extra_args + ["--", filename]
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=dir_path)
out, _ = p.communicate("".encode('utf-8', errors='strict'))
out_str = out.decode('utf-8')
return out_str
def run_pandoc(self, source, base="markdown", extensions=[], to="json", extra_args=[]):
ext_str = ""
if isinstance(extensions, list):
for ext in extensions:
if ext.startswith('#'):
continue
if ext.startswith('+') or ext.startswith('-'):
ext_str = ext_str + ext
elif len(ext) > 0:
ext_str = ext_str + '+' + ext
elif isinstance(extensions, dict):
for ext_key in extensions:
# TODO catch 'illegal' ext_keys (containing spaces for example)
ext = extensions[ext_key]
if "ignore" in ext and ext["ignore"]:
continue
flag='+'
if "enabled" in ext and not ext["enabled"]:
flag='-'
ext_str = ext_str + flag + ext_key
#print(ext_str)
pandoc_bin = "pandoc"
args = [pandoc_bin, "-f", base + ext_str, "-t", to] + extra_args
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, _ = p.communicate(source.encode('utf-8', errors='strict'))
out_str = out.decode('utf-8')
#print("----------------------")
#print(out_str)
#print("----------------------")
#json_dict = json.loads(out.decode('utf-8'))
return out_str
from jinja2 import Environment, FileSystemLoader
import os
class Writer:
def __init__(self, config, context, output_dir, theme_dir):
self.config = config
self.context = context
self.output_dir = output_dir
self.theme_dir = theme_dir
self.env = Environment(
loader=FileSystemLoader(theme_dir + "/templates"),
autoescape=False
)
print("templates: ", self.env.list_templates())
def write_template(self, template, path, lang, extra_context):
tmpl = self.env.get_template(template)
pathsplit = path.split('/')
#pathsplit.remove('.') # TODO remove all not just one
#pathsplit.remove('..') # TODO remove all not just one
siteurl = "."
if self.config['relative_urls']:
count = len(pathsplit) - 1
for i in range(count):
siteurl = siteurl + '/..'
else:
siteurl = config['siteurl']
# render template
context = {}
context.update(self.context)
context["config"] = self.config
context["theme"] = self.config['theme']
context["template"] = template
context["t"] = self.config["lang"] # translate
context["l"] = lang # current language
context["path"] = path
context["siteurl"] = siteurl
context.update(extra_context)
out = tmpl.render(context)
# write file
self.write_file(path, out)
def write_file(self, path, out, mode="w"):
# write to file
fullpath = self.output_dir + '/' + path
directory = os.path.dirname(fullpath)
print("fullpath: ", fullpath)
print("dir: ", directory)
os.makedirs(directory, exist_ok=True)
with open(fullpath, mode) as f:
f.write(out)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment