Source code for posty.site

from __future__ import absolute_import

from collections import Counter
import datetime
import os.path
import shutil

from .config import Config
from .exceptions import PostyError
from .page import Page
from .post import Post
from posty.renderer import (
    HtmlRenderer,
    JsonRenderer,
    RssRenderer,
    AtomRenderer,
    Posty1RedirectRenderer,
)
from .util import slugify


[docs] class Site(object): """ Representation of an entire site with posts and pages. This is the main class that conrols everything. :param site_path: Path to the directory containing site content (pages, posts, templates) :param config_path: Path to the config file, defaults to ``$SITE_PATH/config.yml`` """ def __init__( self, site_path: str = ".", config_path: str | None = None, config: Config | None = None, ) -> None: self.site_path = site_path if config: self.config = config else: if config_path: self.config_path = config_path else: self.config_path = os.path.join(site_path, "config.yml") self.config = Config.from_yaml(self.config_path) self.pages: list[Page] = [] self.posts: list[Post] = [] self.tags: list[str] = [] self.loaded = False
[docs] def init(self) -> None: """ Initialize a new Posty site at the given path """ skel_path = os.path.join(os.path.dirname(__file__), "skel") for thing in os.listdir(skel_path): src = os.path.join(skel_path, thing) dst = os.path.join(self.site_path, thing) if os.path.exists(dst): print("{} already exists, not overwriting".format(thing)) else: if os.path.isdir(src): shutil.copytree(src, dst) elif os.path.isfile(src): shutil.copy(src, dst)
[docs] def load(self) -> None: """ Load the site from files on disk into our internal representation """ self._load_pages() self._load_posts() self.loaded = True
[docs] def render(self, output_path: str = "build") -> None: """ Render the site with the various renderers * HTML * JSON * RSS (if ``feeds.rss`` is True in the config) * Atom (if ``feeds.atom`` is True in the config) """ HtmlRenderer(self, output_path=output_path).render_site() JsonRenderer(self, output_path=output_path).render_site() if self.config.feeds.rss: RssRenderer(self, output_path=output_path).render_site() if self.config.feeds.atom: AtomRenderer(self, output_path=output_path).render_site() if self.config.compat.redirect_posty1_urls: Posty1RedirectRenderer(self, output_path=output_path).render_site()
def _load_pages(self) -> None: pages = [] page_dir = os.path.join(self.site_path, "pages") for filename in os.listdir(page_dir): contents = open(os.path.join(page_dir, filename)).read() pages.append(Page.from_yaml(contents, config=self.config)) self.pages = sorted(pages, key=lambda x: x.title.lower()) def _load_posts(self) -> None: posts = [] tags = [] # Load each post post_dir = os.path.join(self.site_path, "posts") for filename in os.listdir(post_dir): contents = open(os.path.join(post_dir, filename)).read() post = Post.from_yaml(contents, config=self.config) posts.append(post) tags.extend(post.tags) self.posts = sorted(posts, key=lambda x: x.date, reverse=True) # uniquify tags and sort by frequency (descending) self.tags = [t for t, c in Counter(tags).most_common()]
[docs] def post(self, slug: str) -> Post: """ Returns a Post object by its slug :param slug: slug of the post to find :returns: A post dict :raises PostyError: if no post could be found """ for post in self.posts: post_slug = post.slug or slugify(post.title) if slug == post_slug: return post else: raise PostyError( "Unable to find post {}. Available posts: {}".format( slug, [slugify(p.title) for p in self.pages] ) )
[docs] def page(self, slug: str) -> Page: """ Returns a Page object by its slug :param slug: slug of the page to find :returns: A page dict :raises PostyError: if no page could be found """ for page in self.pages: page_slug = page.slug or slug == slugify(page.title) if slug == page_slug: return page else: raise PostyError( "Unable to find post {}. Available posts: {}".format( slug, [p.slug or slugify(p.title) for p in self.pages] ) )
@property def copyright(self) -> str: """ Returns a string of the copyright info, based on the configured author and the years of the first and last post """ first_post = self.posts[-1] last_post = self.posts[0] copyright = "Copyright {start} - {end}, {author}".format( author=self.config.author, start=first_post.date.year, end=last_post.date.year, ) return copyright
[docs] def new_post(self, name: str = "New Post") -> None: """ Create a new post in the site directory from the skeleton post """ post_dir = os.path.join(self.site_path, "posts") if not os.path.exists(post_dir): raise PostyError("You must initialize the site first") date = datetime.date.today() filename = "{}_{}.yaml".format(date, slugify(name)) post_path = os.path.join(post_dir, filename) skel_path = os.path.join( os.path.dirname(__file__), "skel/posts/1970-01-01_new-post.yaml" ) post = Post.from_yaml(open(skel_path).read(), config=self.config) post.title = name post.date = date with open(post_path, "w") as output_file: output_file.write(post.to_yaml())
[docs] def new_page(self, name: str = "New Page") -> None: """ Create a new page in the site directory from the skeleton page """ page_dir = os.path.join(self.site_path, "pages") if not os.path.exists(page_dir): raise PostyError("You must initialize the site first") filename = "{}.yaml".format(slugify(name)) page_path = os.path.join(page_dir, filename) skel_path = os.path.join(os.path.dirname(__file__), "skel/pages/new-page.yaml") page = Page.from_yaml(open(skel_path).read(), config=self.config) page.title = name with open(page_path, "w") as output_file: output_file.write(page.to_yaml())