Source code for nefelibata.post
import logging
import time
from datetime import datetime
from email.header import decode_header
from email.header import make_header
from email.parser import Parser
from email.utils import formatdate
from email.utils import parsedate_to_datetime
from pathlib import Path
from typing import Any
from typing import cast
from typing import Dict
from typing import List
import markdown
from bs4 import BeautifulSoup
_logger = logging.getLogger(__name__)
[docs]class Post:
def __init__(self, file_path: Path):
self.file_path = file_path
with open(file_path) as fp:
self.parsed = Parser().parse(fp)
self.markdown = self.parsed.get_payload(decode=False)
self.html = markdown.markdown(
self.markdown, extensions=["codehilite"], output_format="html5",
)
self.update_metadata()
@property
def title(self) -> str:
return str(make_header(decode_header(self.parsed["subject"])))
@property
def summary(self) -> str:
if self.parsed["summary"]:
return str(make_header(decode_header(self.parsed["summary"])))
soup = BeautifulSoup(self.html, "html.parser")
if soup.p:
summary: str = soup.p.text.replace("\n", " ")
if len(summary) > 140:
summary = summary[:139] + "\u2026"
return summary
return "No summary."
@property
def date(self) -> datetime:
if not self.parsed["date"]:
raise Exception(f"Missing date on file {self.file_path}")
return parsedate_to_datetime(self.parsed["date"])
@property
def url(self) -> str:
post_directory = self.file_path.parent
# TODO: this should be relative to root/posts instead
return str(
self.file_path.relative_to(post_directory.parent).with_suffix(".html"),
)
@property
def up_to_date(self) -> bool:
html = self.file_path.with_suffix(".html")
return html.exists() and html.stat().st_mtime >= self.file_path.stat().st_mtime
[docs] def update_metadata(self) -> None:
"""Automatically generate date and subject headers.
"""
modified = False
if not self.parsed.get("date"):
date = self.file_path.stat().st_mtime
self.parsed["date"] = formatdate(date, localtime=True)
modified = True
if not self.parsed.get("subject"):
# try to find an H1 tag or use the filename
soup = BeautifulSoup(self.html, "html.parser")
del self.parsed["subject"] # needed to overwrite
if soup.h1:
self.parsed["subject"] = soup.h1.text
else:
# use directory name
self.parsed["subject"] = str(self.file_path.parent.name)
modified = True
if modified:
self.save()
[docs] def save(self) -> None:
"""Save post back."""
with open(self.file_path, "w") as fp:
fp.write(str(self.parsed))
[docs]def get_posts(root: Path) -> List[Post]:
"""Return list of posts for a given root directory.
"""
return [Post(source) for source in (root / "posts").glob("**/*.mkd")]