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
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: parsed = parsedate(self.parsed["date"]) if parsed is None: raise Exception(f"Missing date on file {self.file_path}") return datetime.fromtimestamp(time.mktime(parsed)) @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")]