title = "".join(x for x in title if x in allowable)
return title
-def paginated_property(f):
- # Add <PROPERTY>.pages and <PROPERTY>.first10 with deep python magic
- class Paginated():
- def __init__(self, lst):
- self.lst = sorted(lst, key=lambda x: x.date, reverse=True)
- def __iter__(self):
- return iter(self.lst)
- def __len__(self):
- return len(self.lst)
- @property
- def pages(self, per_page=10):
- for start in range(0, len(self.lst), per_page):
- yield self.lst[start:start+per_page]
- @property
- def first10(self):
- return self.lst[:10]
- @property
- def length(self):
- return len(self.lst)
- class AnonProperty():
- def __init__(self, fget):
- self.fget = fget
- def __set_name__(self, owner, name):
- self._name = "_" + name
- def __get__(self, obj, objtype=None):
- return Paginated(self.fget(obj))
-
- return AnonProperty(f)
-
def flag_last(l):
l = list(l)
for x in l[:-1]:
class Templatable(PseudoMap):
use_layout = True
+ is_paginated = False
def __init__(self, blog):
self.blog = blog
return content.encode("utf8")
def output(self):
- output = self.content()
- self.output_path.parent.mkdir(parents=True, exist_ok=True)
- with open(self.output_path, "wb") as f:
- f.write(output)
- # TODO: Add a 'paginated' property that Tag, Category, Author, and index.html can all use
+ per_page = self.blog.posts_per_page
+ if not self.is_paginated or len(self.posts) <= per_page:
+ if self.is_paginated:
+ self.current_page = {"posts": self.posts}
+ self.pagination = ""
+ output = self.content()
+ self.output_path.parent.mkdir(parents=True, exist_ok=True)
+ with open(self.output_path, "wb") as f:
+ f.write(output)
+ else: # Paginated output
+ paginated_url_template = self.blog["{}_paginated_url".format(self.type)]
+ output_path_template = self.blog["{}_paginated_destination".format(self.type)]
+
+ pages = []
+ for start in range(0, len(self.posts), per_page):
+ number = start//per_page + 1
+ p = {
+ "posts": self.posts[start:start+per_page],
+ "page_num": number,
+ }
+ p["url"] = mustache.render(paginated_url_template, collections.ChainMap(p, self.context))
+ p["output_path"] = Path(mustache.render(output_path_template, collections.ChainMap(p, self.context)))
+ pages.append(p)
+
+ for page in pages:
+
+ self.current_page = page
+ pagination_context = collections.ChainMap({
+ "current_page": page,
+ "pages": [
+ {
+ "is_current": p == page,
+ "page_num": p["page_num"],
+ "url": p["url"],
+ } for i, p in enumerate(pages)
+ ]
+ }, self.context)
+
+ self.pagination = self.render_template(self.blog, "pagination", pagination_context)
+ content = self.render_template(self.blog, self.type, self.context)
+ assert self.use_layout
+ if self.use_layout:
+ content = self.render_template(self.blog, "layout", collections.ChainMap({
+ "content": content,
+ }, pagination_context, self, self.blog))
+ content = content.encode("utf8")
+
+ page["output_path"].parent.mkdir(parents=True, exist_ok=True)
+ with open(page["output_path"], "wb") as f:
+ f.write(content)
@property
def context(self):
def post(self):
return '<div class="entry-content">{}</div>'.format(markdown2html(self.md))
- def content_combined(self):
- return self.render_template(self.blog, "post", self.context).encode("utf8")
-
@property
def date_rfc822(self):
return self.date.strftime(RFC822)
def has_categories(self):
return len(self.categories) > 0
+ def content(self):
+ c = super().content()
+ if self.blog.local:
+ return self.blog.localize_absolute_links(c.decode("utf8")).encode("utf8")
+ else:
+ return c
+
@property
def html(self):
- return self.render_template(self.blog, self.type, collections.ChainMap({
+ h = self.render_template(self.blog, self.type, collections.ChainMap({
"main_display": False,
}, self.context))
+ if self.blog.local:
+ h = self.blog.localize_absolute_links(h)
+ return h
+
class Tag(Templatable):
+ is_paginated = True
def __init__(self, tag, blog):
super().__init__(blog)
self.tag = tag
self._posts = set()
self.slug = url_slug(tag)
+ @property
+ def posts(self):
+ return sorted(self._posts, key=lambda p: p.date, reverse=True)
+
def add_post(self, post):
self._posts.add(post)
- @paginated_property
- def posts(self):
- return self._posts
-
@property
def num_posts(self):
- return len(self.posts)
+ return len(self._posts)
def __hash__(self):
return hash(self.tag)
class Category(Tag):
pass
+class Author(Tag):
+ pass
+
class Page(Templatable):
- def __init__(self, page_name, blog, use_layout=None):
+ def __init__(self, page_name, blog, **kw):
super().__init__(blog)
self.page_name = page_name
- if use_layout is not None:
- self.use_layout = use_layout
+ for k, v in kw.items():
+ setattr(self, k, v)
@property
def type(self):
return self.page_name
-class Author(Tag):
- pass
-
class Image(Templatable):
use_layout = False
pass # TODO
self.tags = {} # Tag -> str
self.categories = {}
self.authors = {}
- self._posts = []
+ self.posts = []
self.now = datetime.datetime.now(datetime.timezone.utc)
self.now_rfc822 = self.now.strftime(RFC822)
self[k] = v
self.feed_url = mustache.render(self.feed_url, self)
- @paginated_property
- def posts(self):
- return self._posts
-
def load_comments(self, stem):
comments_path = Path(self.comments_dir) / (stem + ".html")
if comments_path.exists():
comments = self.load_comments(post_input_path.stem)
assert fm["has-comments"] == (comments is not None)
self.add_post(Post(fm, self, comments))
+ self.posts = sorted(self.posts, key=lambda post: post.date, reverse=True)
def add_post(self, post):
- self._posts.append(post)
+ self.posts.append(post)
for tag in post.tags:
tag.add_post(post)
@property
def pages(self):
return [
- Page("index", self),
- Page("feed", self, use_layout=False),
+ Page("index", self, is_paginated=True, posts=blog.posts),
+ Page("feed", self, use_layout=False, posts=blog.posts[:10]),
]
@property
return Templatable.render_template(Templatable, blog, "tagcloud", self)
+ def localize_absolute_links(self, t):
+ return t.replace('href="/', f"href=\"{self.web_root}/").replace('src="/', f"src=\"{self.web_root}/")
+
def clean(self):
assert self.destination
os.system("rm -rf \"{}\"/*".format(self.destination))
title: 'blog of zachary "za3k" vance'
# For search bar
domain: "blog2.za3k.com"
+posts_per_page: 10
post_dir: "posts"
comments_dir: "comments"
index_template: "templates/index.mustache.html"
feed_template: "templates/feed.mustache.html"
layout_template: "templates/layout.mustache.html"
+pagination_template: "templates/pagination.mustache.html"
post_template: "templates/post.mustache.html"
tag_template: "templates/tag.mustache.html"
tagcloud_template: "templates/tagcloud.mustache.html"
# Non-local mode, preferred link address
author_url: "{{web_root}}/author/{{slug}}/"
+author_paginated_url: "{{web_root}}/author/{{slug}}/{{page_num}}/"
category_url: "{{web_root}}/category/{{slug}}/"
+category_paginated_url: "{{web_root}}/category/{{slug}}/{{page_num}}/"
tag_url: "{{web_root}}/tag/{{slug}}/"
+tag_paginated_url: "{{web_root}}/tag/{{slug}}/{{page_num}}/"
index_url: "{{web_root}}"
+index_paginated_url: "{{web_root}}/page/{{page_num}}"
post_url: "{{web_root}}/{{id}}/"
feed_url: "{{web_root}}/feed/"
author_destination: "{{destination}}/author/{{slug}}.html"
+author_paginated_destination: "{{destination}}/author/{{slug}}.{{page_num}}.html"
category_destination: "{{destination}}/category/{{slug}}.html"
+category_paginated_destination: "{{destination}}/category/{{slug}}.{{page_num}}.html"
index_destination: "{{destination}}/page/index.html"
+index_paginated_destination: "{{destination}}/page/index.{{page_num}}.html"
feed_destination: "{{destination}}/page/feed.xml"
image_destination: "{{destination}}/images/{{image}}"
page_destination: "{{destination}}/{{page}}"
post_destination: "{{destination}}/posts/{{id}}.html"
static_destination: "{{destination}}/{{relative_path}}"
tag_destination: "{{destination}}/tag/{{slug}}.html"
+tag_paginated_destination: "{{destination}}/tag/{{slug}}.{{page_num}}.html"
+
+category_destination: "{{destination}}/category/{{slug}}.html"
+index_destination: "{{destination}}/page/index.html"
+tag_destination: "{{destination}}/tag/{{slug}}.html"