from collections import defaultdict
import jinja2
import os
from urllib.parse import urljoin
from typing import Any, TYPE_CHECKING
from posty import util
from posty.page import Page
from posty.post import Post
from posty.renderer.base import Renderer
from posty.renderer.util import markdown_func, media_url_func, absolute_url_func
if TYPE_CHECKING:
from posty.site import Site
# Route reference
# / Posts
# /page/:page/ Posts page #:page
# /tag/:tag/ Posts matching tag :tag
# /tag/:tag/page/:page/ Posts matching tag :tag, page #:page
#
# /:year/:month/:slug/ Single post
#
# /:slug/ Page matching :slug
[docs]
class HtmlRenderer(Renderer):
"""
Renderer that outputs HTML files
"""
def __init__(self, site: "Site", output_path: str = "build") -> None:
"""
:param site:
a Site object to build
:param output_path:
path relative to the Site's path to put rendered HTML files into
"""
super(HtmlRenderer, self).__init__(site, output_path=output_path)
template_path = os.path.join(site.site_path, "templates")
self.jinja_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_path),
)
filters = self.jinja_env.filters
filters["markdown"] = markdown_func(self.site)
filters["media_url"] = media_url_func(self.site)
filters["absolute_url"] = absolute_url_func(self.site)
def _render_file(
self,
path: str,
template: jinja2.Template,
**kwargs: Any,
) -> None:
with open(path, "w") as f:
f.write(template.render(**kwargs))
[docs]
def render_site(self) -> None:
"""
Given a Site object, render all of its components
:param site: a loaded Site object
"""
for post in self.site.posts:
self.render_post(post)
for page in self.site.pages:
self.render_page(page)
self.render_site_posts()
self.render_site_tags()
[docs]
def render_posts(
self,
posts: list[Post],
prefix: str = "",
template_name: str = "posts.html",
) -> None:
"""
Render a list of posts as sets of pages where each page has
``num_posts_per_page`` posts. Each page of posts will be rendered to
the path page/:page/index.html relative to the Renderer output_path
If ``prefix`` is given, add that will be put in between the output_path
and page path. For example if the prefix is 'tags/foo/' then a page
path would look like 'tags/foo/page/:page/index.html'
"""
if prefix and prefix[-1] != "/":
prefix += "/"
template = self.jinja_env.get_template(template_name)
groups = util.bucket(posts, self.site.config.num_posts_per_page)
base_page_url = self.site.config.base_url
if prefix:
base_page_url = urljoin(base_page_url, prefix)
base_page_url = urljoin(base_page_url, "page/")
output_path = os.path.join(self.output_path, prefix)
if not os.path.exists(output_path):
os.makedirs(output_path)
# Render the first group as index.html
posts = groups.pop(0)
dst_file = os.path.join(output_path, "index.html")
next_page_url = None
if len(groups) > 0:
next_page_url = urljoin(base_page_url, str(2) + "/")
self._render_file(
dst_file, template, site=self.site, posts=posts, next_page_url=next_page_url
)
# Render the rest
last_page = len(groups) + 1
for page, posts in enumerate(groups, start=2):
dst_path = os.path.join(output_path, "page", str(page) + "/")
if not os.path.exists(dst_path):
os.makedirs(dst_path)
dst_file = os.path.join(dst_path, "index.html")
if page == 2:
prev_page_url = urljoin(self.site.config.base_url, prefix)
else:
prev_page_url = urljoin(base_page_url, str(page - 1) + "/")
next_page_url = None
if page != last_page:
next_page_url = urljoin(base_page_url, str(page + 1) + "/")
self._render_file(
dst_file,
template,
site=self.site,
posts=posts,
prev_page_url=prev_page_url,
next_page_url=next_page_url,
)
[docs]
def render_site_posts(self) -> None:
"""
Renders all of the multi-post pages, N per page
"""
self.ensure_output_path()
self.render_posts(self.site.posts)
[docs]
def render_page(self, page: Page, template_name: str = "page.html") -> None:
"""
:param page: a Page object
"""
self.ensure_output_path()
dst_dir = os.path.join(self.output_path, page.path_on_disk())
dst_file = os.path.join(dst_dir, "index.html")
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
template = self.jinja_env.get_template(template_name)
self._render_file(dst_file, template, site=self.site, page=page)
[docs]
def render_post(self, post: Post, template_name: str = "post.html") -> None:
"""
:param post: a Post object
"""
self.ensure_output_path()
dst_dir = os.path.join(self.output_path, post.path_on_disk())
dst_file = os.path.join(dst_dir, "index.html")
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
template = self.jinja_env.get_template(template_name)
self._render_file(dst_file, template, site=self.site, post=post)