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
from jinja2 import Environment
from jinja2 import FileSystemLoader

_logger = logging.getLogger(__name__)


[docs]class Post: def __init__(self, root: Path, file_path: Path): self.root = root 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: str = markdown.markdown( self.markdown, extensions=["codehilite"], output_format="html5", ) self.update_metadata()
[docs] def render(self, config: Dict[str, Any]) -> str: post_type = self.parsed.get("type") if not post_type: return self.html env = Environment( loader=FileSystemLoader( str(self.root / "templates" / config["theme"] / "posts"), ), ) template = env.get_template(f"{post_type}.html") return template.render(post=self)
@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: return str( self.file_path.relative_to(self.root / "posts").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(root, source) for source in (root / "posts").glob("**/*.mkd")]