Add base Markdown blog structure.
This commit is contained in:
parent
bdf397335c
commit
a8c4d6ef48
157
scripts/blog.py
Normal file
157
scripts/blog.py
Normal file
@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
MARKDOWN_EXTENSION = '.md'
|
||||
EXTENSIONS = [
|
||||
MARKDOWN_EXTENSION,
|
||||
]
|
||||
|
||||
MARKDOWN_EXTRA_FEATURES = [
|
||||
# See more in: https://python-markdown.github.io/extensions/
|
||||
'markdown.extensions.fenced_code',
|
||||
'markdown.extensions.codehilite',
|
||||
'markdown.extensions.extra',
|
||||
]
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
import jinja2
|
||||
import shutil
|
||||
|
||||
import yaml
|
||||
import markdown
|
||||
import re
|
||||
from unidecode import unidecode
|
||||
|
||||
NIKOLA_DATE_RE = re.compile(r'^([0-2]\d|30|31)\.(0\d|1[012])\.(\d{4}), (\d{1,2}):(\d{2})$')
|
||||
|
||||
COMPLETE_DATE_RE = re.compile(r'^(\d{4})-(0\d|1[012])-([0-2]\d|30|31) '
|
||||
+ r'(\d{2}):(\d{2})(:\d{2})( .+)?$')
|
||||
SLUG_HYPHENATE_RE = re.compile(r'[\s\-]+')
|
||||
SLUG_REMOVE_RE = re.compile(r'[^\s\-a-zA-Z0-9]*')
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
STATIC_PATH = os.path.join(ROOT_DIR, 'static')
|
||||
ARTICLE_TEMPLATE_NAME = 'article.tmpl.html'
|
||||
STATIC_RESOURCES = (
|
||||
('style.css', 'css/style.css'),
|
||||
)
|
||||
|
||||
JINJA_ENV = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(STATIC_PATH),
|
||||
autoescape=jinja2.select_autoescape()
|
||||
)
|
||||
ARTICLE_TEMPLATE = JINJA_ENV.get_template(ARTICLE_TEMPLATE_NAME)
|
||||
|
||||
def parse_nikola_date(match):
|
||||
return datetime.datetime(year=int(match.group(3)),
|
||||
month=int(match.group(2)),
|
||||
day=int(match.group(1)),
|
||||
hour=int(match.group(4)),
|
||||
minute=int(match.group(5)))
|
||||
|
||||
def parse_complete_date(match):
|
||||
return datetime.datetime.strptime(match.group(0), '%Y-%m-%d %H:%M:%S %Z%z')
|
||||
|
||||
def slugify(title):
|
||||
"""
|
||||
Made for compatibility with Nikola's slugify within CodigoParaLlevar blog.
|
||||
"""
|
||||
slug = unidecode(title).lower()
|
||||
slug = SLUG_REMOVE_RE.sub('', slug)
|
||||
slug = SLUG_HYPHENATE_RE.sub('-', slug)
|
||||
|
||||
return slug.strip()
|
||||
|
||||
def read_markdown(path):
|
||||
with open(path, 'rt') as f:
|
||||
data = f.read()
|
||||
if data.startswith('---'):
|
||||
start = data.index('\n')
|
||||
if '---\n' not in data[start:]:
|
||||
raise Exception('Front matter not finished on: {}'.format(path))
|
||||
front_matter_str, content = data[start:].split('---\n', 1)
|
||||
front_matter = yaml.load(front_matter_str, Loader=yaml.SafeLoader)
|
||||
else:
|
||||
raise Exception('Front matter is needed for proper rendering. Not found on: {}'.format(
|
||||
path
|
||||
))
|
||||
doc = markdown.markdown(content, extensions=MARKDOWN_EXTRA_FEATURES)
|
||||
return doc, front_matter
|
||||
|
||||
|
||||
def get_out_path(front_matter):
|
||||
if 'date' in front_matter:
|
||||
if m := NIKOLA_DATE_RE.match(front_matter['date']):
|
||||
front_matter['date'] = parse_nikola_date(m)
|
||||
elif m := COMPLETE_DATE_RE.match(front_matter['date']):
|
||||
front_matter['date'] = parse_complete_date(m)
|
||||
else:
|
||||
raise NotImplementedError('Unknown date format: {}'.format(
|
||||
front_matter['date']))
|
||||
else:
|
||||
raise Exception('No date found on: {}'.format(
|
||||
path
|
||||
))
|
||||
|
||||
if 'slug' not in front_matter:
|
||||
if 'title' not in front_matter:
|
||||
raise Exception('No title found on: {}'.format(
|
||||
path
|
||||
))
|
||||
|
||||
front_matter['slug'] = slugify(front_matter['title'])
|
||||
|
||||
out_path = os.path.join(str(front_matter['date'].year), front_matter['slug'])
|
||||
return out_path
|
||||
|
||||
|
||||
def load_all(top_dir_relative):
|
||||
top = os.path.abspath(top_dir_relative)
|
||||
|
||||
docs = []
|
||||
|
||||
for root, dirs, files in os.walk(top):
|
||||
for name in files:
|
||||
if all([not name.endswith(ext) for ext in EXTENSIONS]):
|
||||
# The logic is negative... but it works
|
||||
continue
|
||||
|
||||
if name.endswith(MARKDOWN_EXTENSION):
|
||||
path = os.path.join(root, name)
|
||||
doc, front_matter = read_markdown(path)
|
||||
out_path = get_out_path(front_matter)
|
||||
docs.append((doc, front_matter, out_path))
|
||||
else:
|
||||
raise NotImplementedError('Unknown filetype: {}'.format(name))
|
||||
|
||||
return docs
|
||||
|
||||
def render_article(doc, f):
|
||||
result = ARTICLE_TEMPLATE.render(content=doc)
|
||||
f.write(result)
|
||||
|
||||
def main(source_top, dest_top):
|
||||
docs = load_all(source_top)
|
||||
for (doc, front_matter, out_path) in docs:
|
||||
doc_full_path = os.path.join(dest_top, out_path)
|
||||
os.makedirs(os.path.dirname(doc_full_path), exist_ok=True)
|
||||
print("==", doc_full_path)
|
||||
with open(doc_full_path + '.html', 'wt') as f:
|
||||
render_article(doc, f)
|
||||
|
||||
for src, dest in STATIC_RESOURCES:
|
||||
target_dest = os.path.join(dest_top, dest)
|
||||
os.makedirs(os.path.dirname(target_dest), exist_ok=True)
|
||||
shutil.copy(os.path.join(STATIC_PATH, src), target_dest)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
|
||||
exit(0)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
||||
main(sys.argv[1], sys.argv[2])
|
2
scripts/requirements.txt
Normal file
2
scripts/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Markdown
|
||||
Jinja2
|
15
static/article.tmpl.html
Normal file
15
static/article.tmpl.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Código para llevar</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<article>
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
6
static/blog.css
Normal file
6
static/blog.css
Normal file
@ -0,0 +1,6 @@
|
||||
body {
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
max-width: 100ex;
|
||||
padding: 0 1ex;
|
||||
}
|
Loading…
Reference in New Issue
Block a user