madblog/madblog/app.py

114 lines
4.1 KiB
Python

import datetime
import os
import re
from glob import glob
from typing import Optional
from flask import Flask, abort
from markdown import markdown
from .config import config
from .latex import MarkdownLatex
class BlogApp(Flask):
def __init__(self, *args, **kwargs):
super().__init__(*args, template_folder=config.templates_dir, **kwargs)
self.pages_dir = os.path.join(config.content_dir, 'markdown')
self.img_dir = config.default_img_dir
self.css_dir = config.default_css_dir
self.fonts_dir = config.default_fonts_dir
if not os.path.isdir(self.pages_dir):
raise FileNotFoundError(self.pages_dir)
img_dir = os.path.join(config.content_dir, 'img')
if os.path.isdir(img_dir):
self.img_dir = os.path.abspath(img_dir)
css_dir = os.path.join(config.content_dir, 'css')
if os.path.isdir(css_dir):
self.css_dir = os.path.abspath(css_dir)
fonts_dir = os.path.join(config.content_dir, 'fonts')
if os.path.isdir(fonts_dir):
self.fonts_dir = os.path.abspath(fonts_dir)
templates_dir = os.path.join(config.content_dir, 'templates')
if os.path.isdir(templates_dir):
self.template_folder = os.path.abspath(templates_dir)
def get_page_metadata(self, page: str) -> dict:
if not page.endswith('.md'):
page = page + '.md'
if not os.path.isfile(os.path.join(self.pages_dir, page)):
abort(404)
metadata = {}
md_file = os.path.join(self.pages_dir, page)
with open(md_file, 'r') as f:
metadata['uri'] = '/article/' + page[:-3]
for line in f.readlines():
if not line:
continue
if not (m := re.match(r'^\[//]: # \(([^:]+):\s*([^)]+)\)\s*$', line)):
break
if m.group(1) == 'published':
metadata[m.group(1)] = datetime.date.fromisoformat(m.group(2))
else:
metadata[m.group(1)] = m.group(2)
if not metadata.get('published'):
# If the `published` header isn't available in the file,
# infer it from the file's creation date
metadata['published'] = datetime.date.fromtimestamp(os.stat(md_file).st_ctime)
return metadata
def get_page(self, page: str, title: Optional[str] = None, skip_header: bool = False):
if not page.endswith('.md'):
page = page + '.md'
metadata = self.get_page_metadata(page)
with open(os.path.join(self.pages_dir, page), 'r') as f:
return render_template(
'article.html',
config=config,
title=title if title else metadata.get('title', config.title),
image=metadata.get('image'),
description=metadata.get('description'),
author=re.match(r'(.+?)\s+<([^>]+>)', metadata['author'])[1] if 'author' in metadata else None,
author_email=re.match(r'(.+?)\s+<([^>]+)>', metadata['author'])[2] if 'author' in metadata else None,
published=(metadata['published'].strftime('%b %d, %Y')
if metadata.get('published') else None),
content=markdown(f.read(), extensions=['fenced_code', 'codehilite', MarkdownLatex()]),
skip_header=skip_header
)
def get_pages(self, with_content: bool = False, skip_header: bool = False) -> list:
return sorted(
[
{
'path': path[len(app.pages_dir)+1:],
'content': self.get_page(path[len(app.pages_dir)+1:], skip_header=skip_header) if with_content else '',
**self.get_page_metadata(os.path.basename(path)),
}
for path in glob(os.path.join(app.pages_dir, '*.md'))
],
key=lambda page: page.get('published', datetime.date.fromtimestamp(0)),
reverse=True
)
app = BlogApp(__name__)
from .routes import *
# vim:sw=4:ts=4:et: