New Version

This commit is contained in:
2019-05-22 20:43:55 +02:00
parent 4a70d9af93
commit f7ef8d8b6f
9 changed files with 228 additions and 20 deletions

2
.gitignore vendored
View File

@@ -94,3 +94,5 @@ Screenshots/
.vscode .vscode
typesheds/ typesheds/
*.db *.db
publish/

View File

@@ -1 +0,0 @@
DIR_PAGES = 'pages'

View File

@@ -1,32 +1,198 @@
import argparse import argparse
import pathlib import os
import shutil
import const from pathlib import Path
from typing import Iterable, List, Union
from dataclasses import dataclass
import markdown
import yaml
from jinja2 import Template, Environment, FileSystemLoader
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
THEMES_PATH = Path(SCRIPT_PATH, "themes")
TEMPLATES_PATH = Path(SCRIPT_PATH, "templates")
ENV = Environment(loader=FileSystemLoader(str(TEMPLATES_PATH)))
@dataclass
class Article:
file_name: str
title: str
created: str
content: str
updated: str = None
@dataclass
class Page:
name: str
pos: int
stub: str
articles: List[Article] = None
is_index: bool = False
@dataclass
class Site:
title: str
author: str
theme: str
pages: List[Page] = None
class PicoPage: class PicoPage:
def __init__(self, base_path): def __init__(self, path, out_path=None):
self.base_path = base_path self.input_path = path
self.out_path = out_path
def read_pages(self): conf = self.read_config(self.input_path)
pages_dir = self.base_path / const.DIR_PAGES if "theme" not in conf:
for file_name in pages_dir.glob('*.md'): conf["theme"] = "default.css"
yield open(file_name).read() else:
conf["theme"] = f"{conf['theme']}.css"
self.theme_path = THEMES_PATH / conf["theme"]
if out_path is None:
self.out_path = self.input_path.parent / "publish"
def main(self): pages = self.read_pages(self.input_path)
print(list(self.read_pages())) self.site = Site(conf["title"], conf["author"], conf["theme"], pages)
article_count = len([a for pages in self.site.pages for a in pages.articles])
print(f"Read {len(self.site.pages)} pages with {article_count} articles")
@staticmethod
def parse_metadata(md: markdown.Markdown) -> Article:
title = "No Title"
try:
title = md.Meta["title"][0]
except KeyError:
pass
created = "Never"
try:
created = md.Meta["created"][0]
except KeyError:
pass
updated = "Never"
try:
updated = md.Meta["updated"][0]
except KeyError:
pass
return Article(content="",
file_name="",
title=title,
created=created,
updated=updated)
@staticmethod
def load_template(path: Path) -> Template:
with open(str(path), "r") as file:
return Template(file.read())
@staticmethod
def read_config(path: Path) -> dict:
conf_path = path / "config.yaml"
if not conf_path.exists():
print(f"Warning: No config file in '{conf_path}'")
return {}
with open(str(conf_path), "r") as file:
try:
return yaml.safe_load(file)
except yaml.YAMLError as exc:
print(exc)
def read_pages(self, path) -> List[Page]:
pages = []
root = Path(path)
# Parse root page
articles = list(self.read_articles(root))
page = Page(name=root.stem,
stub="index",
pos=0,
is_index=True,
articles=sorted(articles, key=lambda a: a.created))
pages.append(page)
# Parse sub pages
for sub in root.glob("**/"):
if sub == root:
continue
articles = list(self.read_articles(sub))
# Ignore sub dirs without markdown files
if len(articles) == 0:
continue
conf = self.read_config(sub)
page = Page(name=conf.get("title", sub.stem),
stub=sub.stem,
pos=conf.get("position", 100),
articles=sorted(articles, key=lambda post: post.created))
pages.append(page)
return sorted(pages, key=lambda p: p.pos)
@staticmethod
def read_files(path: Path):
for file_name in path.glob('*.md'):
yield file_name.stem, open(str(file_name)).read()
def read_articles(self, path: Path) -> Iterable[Article]:
md = markdown.Markdown(extensions=['meta', 'extra'])
for name, data in self.read_files(path):
html = md.convert(data)
article = self.parse_metadata(md)
article.file_name = name
article.content = html
yield article
def write_output(self):
if Path.exists(self.out_path):
shutil.rmtree(self.out_path)
Path.mkdir(self.out_path)
template = ENV.get_template("page.html")
for page in self.site.pages:
page_vars = {
"site_title": self.site.title,
"page_title": page.name,
"theme": self.site.theme,
"pages": self.site.pages,
"current_page": page
}
html = template.render(page_vars)
path = self.out_path / f"{page.stub}.html"
with open(str(path), "w") as outfile:
outfile.write(html)
shutil.copyfile(THEMES_PATH / self.site.theme, self.out_path / self.site.theme)
print(f"Finished. {len(self.site.pages)} HTML files written.")
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Generate static html pages.') parser = argparse.ArgumentParser(description='Generate static html pages.')
parser.add_argument( parser.add_argument(
'path', 'path',
type=pathlib.Path, type=Path,
default=pathlib.Path('.'), default=Path('.'),
help='Base path of the website files.') help='Base path of the website files.')
parser.add_argument(
'out_path',
type=Path,
nargs='?',
help='Output path for the generated website.')
args = parser.parse_args() args = parser.parse_args()
pico = PicoPage(args.path) pico = PicoPage(args.path, args.out_path)
pico.main() pico.write_output()

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ site_title }} - {{ page_title }}</title>
<link href="{{ theme }}" rel="stylesheet" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<ul>
{% for page in pages %}
{% if page.is_index %}
<li><a href="{{ page.stub }}.html">{{ site_title }}</a></li>
{% else %}
<li><a href="{{ page.stub }}.html">{% if current_page.stub == page.stub %}>> {% endif %}{{ page.name }}</a></li>
{% endif %}
{% endfor %}
</ul>
<div class="content">
{% for article in current_page.articles %}
{{ article.content}}
{% endfor %}
</div>
<div class="footer">
<p>
Created with <a href="#">picopage</a>
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
body {
}
.content{
border: 1px solid gray;
border-radius: 5px;
margin: 5px;
padding: 5px;
}
.content > h1{
margin: 10px 0;
}

View File

@@ -1,3 +0,0 @@
# This is a test page
there will be something meaningful here in the future