Commit 0e7b1aad authored by ammongit's avatar ammongit

First version of generate.py

parent 54a9033f
# Artifacts
/gen/
/output/
__pycache__/
*.py[cdo]
......
## Blog
Contains blog entries, as well as a small "build" system to create HTML files.
Requires Python 3.5+, and [`markdown`](https://www.pell.portland.or.us/~orc/Code/discount/)
somewhere on your `$PATH`.
Setup:
```sh
$ pip3 install -r requirements.txt
```
Usage:
```sh
$ python3 generate.py
$ cp -a output/* /path/to/html/server
```
#
# __init__.py
#
# blog - Easily generate blog HTML
# Copyright (c) 2018 Ammon Smith
#
# blog is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#
__version__ = '0.0.1'
#
# __main__.py
#
# blog - Easily generate blog HTML
# Copyright (c) 2018 Ammon Smith
#
# blog is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#
if __name__ == '__main__':
pass
#
# generate.py
#
# blog - Easily generate blog HTML
# Copyright (c) 2018 Ammon Smith
#
# blog is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#
from collections import defaultdict
import itertools
import os
import re
import subprocess
import sys
import yaml
# Configurable constants
INPUT_DIR = 'posts'
OUTPUT_DIR = 'output'
INDEX_FILENAME = 'index.html'
INDEX_OUTPUT_FILENAME = 'index-{}.html'
INDEX_TEMPLATE_HTML_PATH = 'index-template.html'
ENTRY_TEMPLATE_HTML_PATH = 'entry-template.html'
POST_TEMPLATE_HTML_PATH = 'post-template.html'
INDEX_SIZE = 20
DATE_FORMAT = '%A, %B %-m %Y'
# Other constants
MARKDOWN_REGEX = re.compile(r'(.*)\.(?:markdown|md)', re.IGNORECASE)
YAML_REGEX = re.compile(r'(.*)\.(?:yaml|yml)', re.IGNORECASE)
with open(INDEX_TEMPLATE_HTML_PATH, 'r') as fh:
INDEX_TEMPLATE_HTML = fh.read()
with open(ENTRY_TEMPLATE_HTML_PATH, 'r') as fh:
ENTRY_TEMPLATE_HTML = fh.read()
with open(POST_TEMPLATE_HTML_PATH, 'r') as fh:
POST_TEMPLATE_HTML = fh.read()
# Structures
class Post:
__slots__ = (
'name',
'markdown_path',
'config_path',
'output_path',
'metadata',
)
def __init__(self, name, md_path, cfg_path):
self.name = name
self.markdown_path = md_path
self.config_path = cfg_path
with open(cfg_path, 'r') as fh:
self.metadata = yaml.safe_load(fh)
date = self.metadata['date']
self.output_path = os.path.join(
OUTPUT_DIR,
str(date.year),
str(date.month),
str(date.day),
self.name,
)
def needs_update(self):
if not os.path.isfile(self.output_path):
return True
cfg_mtime = os.stat(self.config_path).st_mtime
md_mtime = os.stat(self.markdown_path).st_mtime
out_mtime = os.stat(self.output_path).st_mtime
return max(md_mtime, cfg_mtime) > out_mtime
def generate(self):
# Format HTML
md_html = subprocess.check_output(["markdown", self.markdown_path])
kwargs = self.metadata_dict()
kwargs['content'] = md_html
html = POST_TEMPLATE_HTML.format(**kwargs)
# Write file
os.makedirs(os.path.dirname(self.output_path))
with open(self.output_path, 'w') as fh:
fh.write(html)
def metadata_dict(self):
return {
'title': self.metadata['title'],
'description': self.metadata['description'],
'date': self.metadata['date'].strftime(DATE_FORMAT),
}
def write_index(number, posts):
# Format HTML for each entry
entry_html = '\n'.join([
ENTRY_TEMPLATE_HTML.format(**post.metadata_dict())
for post in posts
])
# Format HTML for the full index
start = INDEX_SIZE * number + 1
html = INDEX_TEMPLATE_HTML.format(
start=start,
end=start + len(posts),
body=entry_html,
)
# Write final HTML
output_path = os.path.join(OUTPUT_DIR, INDEX_OUTPUT_FILENAME.format(number))
with open(output_path, 'w') as fh:
fh.write(html)
def chunks(iterable, size):
it = iter(iterable)
while True:
chunk = tuple(itertools.islice(it, size))
if chunk:
yield chunk
else:
return
if __name__ == '__main__':
# Change directory
os.chdir(os.path.dirname(sys.argv[0]))
# Generate path tree
# {name : [markdown_path, yaml_path]}
path_tree = defaultdict(lambda: [None, None])
for filename in os.listdir(INPUT_DIR):
match = MARKDOWN_REGEX.match(filename)
if match is not None:
name = match[1]
path_tree[name][0] = filename
continue
match = YAML_REGEX.match(filename)
if match is not None:
name = match[1]
path_tree[name][1] = filename
continue
# Check path tree
posts = []
for name, (md_path, cfg_path) in path_tree.items():
if md_path is None or cfg_path is None:
print("ERROR: Missing file for '{}'".format(name))
exit(1)
posts.append(Post(name, md_path, cfg_path))
# Generate posts
for post in posts:
if post.needs_update():
print("GEN: {}".format(post.output_path))
post.generate()
else:
print("SKIP: {}".format(post.output_path))
continue
# Sort posts by date
posts.sort(lambda x: x.metadata['date'])
# Generate indices
for idx, idx_posts in enumerate(chunks(posts, INDEX_SIZE)):
write_index(idx, idx_posts)
# Symlink final index
index_zero = os.path.join(OUTPUT_DIR, INDEX_OUTPUT_FILENAME.format(0))
index_main = os.path.join(OUTPUT_DIR, INDEX_FILENAME)
os.symlink(index_zero, index_main)
# Finished
print("DONE.")
## Sample Blog Post
Lorem ipsum dolor sit amet, **consectetur adipiscing** elit. Fusce _sit_ amet rhoncus neque.
Nunc at finibus lacus. Nullam pretium massa non pulvinar convallis. Proin ac egestas enim.
Fusce eget:
* turpis eget
* velit tincidunt posuere
* nulla pharetra mi sit amet
* turpis malesuada
[Morbi volutpat](https://example.com) at elit at lacinia. In _mattis_ mi sed ultrices rutrum.
Ut tempus, odio nec egestas laoreet, ex nisi congue est, a placerat ipsum quam nec nulla. Vestibulum mi odio, finibus eu felis vitae, mattis tincidunt leo. **Suspendisse** potenti. Nunc vel commodo elit. Nullam vestibulum nulla purus, sed laoreet leo commodo imperdiet. Etiam arcu tortor, interdum sed sapien quis, feugiat tempus quam.
---
title: Lorem Ipsum
date: 2001-10-24
description: "A powerful space lord invades Earth. Will
the member nations on the United Nations Interstellar Protection Commission
be successful?"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment