# -*- coding: utf-8 -*- import posixpath from datetime import date from itertools import chain from werkzeug.utils import cached_property from lektor.pluginsystem import Plugin from lektor.sourceobj import VirtualSourceObject from lektor.build_programs import BuildProgram from lektor.utils import build_url, parse_path class BlogArchive(VirtualSourceObject): def __init__(self, record, plugin, items=None): VirtualSourceObject.__init__(self, record) self.plugin = plugin self._items = items @property def path(self): return self.record.path + '@blog-archive' template_name = 'blog-archive/index.html' @property def parent(self): return self.record @cached_property def year_archives(self): years = set() for item in self.record.children: pub_date = self.plugin.get_pub_date(item) if pub_date: years.add(pub_date.year) return [BlogYearArchive(self.record, self.plugin, year=year) for year in sorted(years)] @property def items(self): if self._items is not None: return self._items rv = list(self._iter_items()) self._items = rv return rv def _iter_items(self): return iter(()) @property def has_any_items(self): if self._items is not None: return bool(self._items) for item in self._iter_items(): return True return False @property def items_by_months(self): months = {} for item in self.items: pub_date = self.plugin.get_pub_date(item) months.setdefault(date(pub_date.year, pub_date.month, 1), []).append(item) return [(BlogMonthArchive(self.record, self.plugin, year=d.year, month=d.month), i) for d, i in sorted(months.items())] def get_archive_url_path(self): return self.plugin.get_url_path('archive_path') @property def url_path(self): return build_url(chain([self.record.url_path.strip('/')], self.get_archive_url_path() or ())) class BlogYearArchive(BlogArchive): template_name = 'blog-archive/year.html' def __init__(self, record, plugin, items=None, year=None): BlogArchive.__init__(self, record, plugin, items) self.year = year def _iter_items(self): for item in self.record.children: pub_date = self.plugin.get_pub_date(item) if pub_date is not None and \ pub_date.year == self.year: yield item @property def path(self): return '%s@blog-archive/%s' % ( self.record.path, self.year, ) @property def parent(self): return BlogArchive(self.record, self.plugin) @property def date(self): return date(self.year, 1, 1) def get_archive_url_path(self): return self.plugin.get_url_path('year_archive_prefix') + [self.year] class BlogMonthArchive(BlogArchive): template_name = 'blog-archive/year.html' def __init__(self, record, plugin, items=None, year=None, month=None): BlogArchive.__init__(self, record, plugin, items) self.year = year self.month = month def _iter_items(self): for item in self.record.children: pub_date = self.plugin.get_pub_date(item) if pub_date is not None and \ pub_date.year == self.year and \ pub_date.month == self.month: yield item @property def path(self): return '%s@blog-archive/%s/%s' % ( self.record.path, self.year, self.month ) @property def parent(self): return BlogYearArchive(self.record, self.plugin, year=self.year) @property def date(self): return date(self.year, self.month, 1) def get_archive_url_path(self): return self.plugin.get_url_path('month_archive_prefix') + [ self.year, self.month] class BlogArchiveBuildProgram(BuildProgram): def produce_artifacts(self): self.declare_artifact( posixpath.join(self.source.url_path, 'index.html'), sources=list(self.source.iter_source_filenames())) def build_artifact(self, artifact): artifact.render_template_into(self.source.template_name, this=self.source) class BlogArchivePlugin(Plugin): name = u'Blog Archive' description = u'Adds archives to a blog.' def get_pub_date(self, post): key = self.get_config().get('pub_date_field', 'pub_date') return post[key] def get_blog_path(self): return self.get_config().get('blog_path', '/blog') def get_url_path(self, name, default='archive'): return parse_path(self.get_config().get(name, default)) def on_setup_env(self, **extra): self.env.add_build_program(BlogArchive, BlogArchiveBuildProgram) @self.env.virtualpathresolver('blog-archive') def blog_archive_resolver(node, pieces): if node.path == self.get_blog_path(): if not pieces: return BlogArchive(node, self) elif len(pieces) == 1 and pieces[0].isdigit(): return BlogYearArchive(node, self, year=int(pieces[0])) elif len(pieces) == 2 and pieces[0].isdigit() \ and pieces[1].isdigit(): return BlogMonthArchive(node, self, year=int(pieces[0]), month=int(pieces[1])) @self.env.urlresolver def archive_urlresolver(node, url_path): if node.path != self.get_blog_path(): return archive_index = self.get_url_path('archive_path') if url_path == archive_index: return BlogArchive(node, self) year_prefix = self.get_url_path('year_archive_prefix') if url_path[:len(year_prefix)] == year_prefix and \ url_path[len(year_prefix)].isdigit() and \ len(url_path) == len(year_prefix) + 1: year = int(url_path[len(year_prefix)]) rv = BlogYearArchive(node, self, year=year) if rv.has_any_items: return rv month_prefix = self.get_url_path('month_archive_prefix') if url_path[:len(month_prefix)] == month_prefix and \ len(url_path) == len(month_prefix) + 2 and \ url_path[len(month_prefix)].isdigit() and \ url_path[len(month_prefix) + 1].isdigit(): year = int(url_path[len(month_prefix)]) month = int(url_path[len(month_prefix) + 1]) rv = BlogMonthArchive(node, self, year=year, month=month) if rv.has_any_items: return rv @self.env.generator def genererate_blog_archive_pages(source): if source.path != self.get_blog_path(): return years = {} months = {} for post in source.children: pub_date = self.get_pub_date(post) if pub_date: years.setdefault(pub_date.year, []).append(post) months.setdefault((pub_date.year, pub_date.month), []).append(post) yield BlogArchive(source, self) for year, items in sorted(years.items()): yield BlogYearArchive(source, self, year=year, items=items) for (year, month), items in sorted(months.items()): yield BlogMonthArchive(source, self, year=year, month=month, items=items)