From b3ff365a8ae6a9486fad40fbdb6f8afc95ffa064 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 9 Jan 2016 10:24:39 +0100 Subject: [PATCH] Documented virtual paths --- content/docs/api/db/obj/contents.lr | 21 +- content/docs/api/db/obj/record/contents.lr | 28 +++ content/docs/api/db/pad/get/contents.lr | 15 +- .../docs/api/db/record/pagination/contents.lr | 23 +- content/docs/api/db/record/parent/contents.lr | 16 -- .../environment/add-build-program/contents.lr | 14 +- .../api/environment/generator/contents.lr | 7 +- .../api/environment/urlresolver/contents.lr | 7 +- .../virtualpathresolver/contents.lr | 63 +++++ content/docs/api/utils/build-url/contents.lr | 32 +++ content/docs/api/utils/join-path/contents.lr | 45 ++++ content/docs/api/utils/parse-path/contents.lr | 25 ++ content/docs/content/alts/contents.lr | 8 + content/docs/content/paths/contents.lr | 96 +++++++ packages/blog-archive/lektor_blog_archive.py | 235 +++++++++--------- templates/blog-archive/month.html | 2 +- templates/blog-archive/year.html | 2 +- templates/blog-layout.html | 3 +- 18 files changed, 501 insertions(+), 141 deletions(-) create mode 100644 content/docs/api/db/obj/record/contents.lr delete mode 100644 content/docs/api/db/record/parent/contents.lr create mode 100644 content/docs/api/environment/virtualpathresolver/contents.lr create mode 100644 content/docs/api/utils/build-url/contents.lr create mode 100644 content/docs/api/utils/join-path/contents.lr create mode 100644 content/docs/api/utils/parse-path/contents.lr create mode 100644 content/docs/content/paths/contents.lr diff --git a/content/docs/api/db/obj/contents.lr b/content/docs/api/db/obj/contents.lr index c41e3d70..b824d3ac 100644 --- a/content/docs/api/db/obj/contents.lr +++ b/content/docs/api/db/obj/contents.lr @@ -29,22 +29,35 @@ Most plugins will not have source objects that actually originate on the file system. This means that their "source" is entirely virtual. Because this is a very common situation there is a base class, the `VirtualSourceObject` which plugins can subclass. The constructor takes one -argument which is the parent source object the virtual source lives below. +argument which is the source record the virtual source lives below. Virtual +sources are separated from the records they belong to with an at sign (`@`) +in the path. The part after the at sign is called the “virtual path”. + +For instance in the below example the canonical path for the object would be +the record's path + `@source`. So if the record was `/hello` then the +path would be `/hello@source`. The true base record it belongs to can be +referenced from the [record :ref](record/) property. ```python from lektor.sourceobj import VirtualSourceObject +from lektor.utils import build_url class Source(VirtualSourceObject): + @property + def path(self): + return self.record.path + '@source' + @property def source_content(self): - with open(self.parent.source_filename) as f: + with open(self.record.source_filename) as f: return f.read().decode('utf-8') @property def url_path(self): - return self.parent.url_path + 'source.txt' + return build_url([self.record.url_path, 'source.txt']) ``` For more information see [add-build-program -:ref](../../environment/add-build-program/). +:ref](../../environment/add-build-program/) as well as +[virtualpathresolver :ref](../../environment/virtualpathresolver/). diff --git a/content/docs/api/db/obj/record/contents.lr b/content/docs/api/db/obj/record/contents.lr new file mode 100644 index 00000000..22561dd7 --- /dev/null +++ b/content/docs/api/db/obj/record/contents.lr @@ -0,0 +1,28 @@ +title: record +--- +summary: A reference to the associated base record. +--- +type: property +--- +version_added: 2.0 +--- +body: + +Most records (other than the root) have a [parent :ref](../parent/). This as +a concept generally makes in most situations. However when virtual sources +come into play the parent often just refers to another virtual source but not +the root record they all belong to. + +This property for the most part refers back to the object itself. However +for virtual sources this refers back to the record that was associated with +the virtual source. Most of the time this matches `parent` but for nested +source objects it's useful to be able to refer back to the base in all cases +without having to walk up the parents. + +## Example + +```html+jinja +{% if this != this.record %} + go back to overview +{% endif %} +``` diff --git a/content/docs/api/db/pad/get/contents.lr b/content/docs/api/db/pad/get/contents.lr index 43eb15e7..c307afcc 100644 --- a/content/docs/api/db/pad/get/contents.lr +++ b/content/docs/api/db/pad/get/contents.lr @@ -17,7 +17,8 @@ The path needs to be absolute with folder separated by slashes. The default behavior is to load the unpaginated version of a record. If you want to select a specific page for pagination, then you need to pass -`page_num` with a valid page number. +`page_num` with a valid page number or you use the virtual path (`@1` for the +first page for instance). ## Examples @@ -34,3 +35,15 @@ Here another example that loads the current page but in another language: {% set other_lang = site.get(this._path, alt='ru') %}

This page in Russian: {{ other_lang.title }} ``` + +## Virtual Paths + +This method can also be used to look up [virtual paths +:ref](../../../../content/paths/). For instance to fetch a specific version of a +pagination you can use a virtual path instead of using the `page_num` +parameter: + +```pycon +>>> pad.get('/blog@3') + +``` diff --git a/content/docs/api/db/record/pagination/contents.lr b/content/docs/api/db/record/pagination/contents.lr index 14542b03..1c8fec77 100644 --- a/content/docs/api/db/record/pagination/contents.lr +++ b/content/docs/api/db/record/pagination/contents.lr @@ -29,7 +29,8 @@ The following attributes exist on the pagination object: | `for_page()` | Returns the record for a different page. The `for_page()` function accepts a page number and returns the record for -the other page. +the other page. For more information also see the virtual path example +below. !! *Changed in Lektor 2.0:* The `for_page()` method was added. @@ -66,3 +67,23 @@ next page link as well as the number of the current page: {% endif %} ``` + +## Virtual Paths + +!! *New in Lektor 2.0:* virtual paths did not exist in earlier Lektor versions. + +The pagination is implemented in a way where each page in the pagination is +a virtual path below the record itself. The value of the path is just the +number of the page. So for instance to link to the second page you can just +do this: + +```html+jinja +Go to Page 2 +``` + +Alternatively you can also use the `for_path()` function which returns the +entire pagination for a page: + +```html+jinja +Go to Page 2 +``` diff --git a/content/docs/api/db/record/parent/contents.lr b/content/docs/api/db/record/parent/contents.lr deleted file mode 100644 index 94567b3c..00000000 --- a/content/docs/api/db/record/parent/contents.lr +++ /dev/null @@ -1,16 +0,0 @@ -title: parent ---- -summary: Returns the parent record for a record. ---- -type: property ---- -body: - -Because Lektor's database is a tree all records with the exception of the -root record have a parent. This can be accessed with this property. - -## Example - -```html+jinja -Up to {{ this.parent.title }} -``` diff --git a/content/docs/api/environment/add-build-program/contents.lr b/content/docs/api/environment/add-build-program/contents.lr index 2257da31..b28c7132 100644 --- a/content/docs/api/environment/add-build-program/contents.lr +++ b/content/docs/api/environment/add-build-program/contents.lr @@ -19,17 +19,22 @@ artifacts out of source objects. ```python from lektor.sourceobj import VirtualSourceObject from lektor.build_programs import BuildProgram +from lektor.utils import build_url class Source(VirtualSourceObject): + @property + def path(self): + return self.record.path + '@source' + @property def source_content(self): - with open(self.parent.source_filename) as f: + with open(self.record.source_filename) as f: return f.read().decode('utf-8') @property def url_path(self): - return self.parent.url_path + 'source.txt' + return build_url([self.record.url_path, 'source.txt']) class SourceBuildProgram(BuildProgram): @@ -43,6 +48,11 @@ class SourceBuildProgram(BuildProgram): this=self.source) env.add_build_program(Source, SourceBuildProgram) + +@env.virtualpathresolver('source') +def resolve_virtual_path(record, pieces): + if not pieces: + return Source(record) ``` And here the example `view_source.html` template: diff --git a/content/docs/api/environment/generator/contents.lr b/content/docs/api/environment/generator/contents.lr index 5b5ef146..9a0252ce 100644 --- a/content/docs/api/environment/generator/contents.lr +++ b/content/docs/api/environment/generator/contents.lr @@ -20,12 +20,17 @@ exists for a page. ```python from lektor.sourceobj import VirtualSourceObject from lektor.db import Record +from lektor.utils import build_url class Source(VirtualSourceObject): + @property + def path(self): + return self.record.path + '@source' + @property def url_path(self): - return self.parent.url_path + 'source.txt' + return build_url([self.record.url_path, 'source.txt']) @env.generator def generate_source_file(node): diff --git a/content/docs/api/environment/urlresolver/contents.lr b/content/docs/api/environment/urlresolver/contents.lr index b96ee0af..2ea2f33b 100644 --- a/content/docs/api/environment/urlresolver/contents.lr +++ b/content/docs/api/environment/urlresolver/contents.lr @@ -19,12 +19,17 @@ URL path segments. ```python from lektor.sourceobj import VirtualSourceObject +from lektor.utils import build_url class Source(VirtualSourceObject): + @property + def path(self): + return self.record.path + '@source' + @property def url_path(self): - return self.parent.url_path + 'source.txt' + return build_url([self.record.url_path, 'source.txt']) @env.urlresolver def match_source_file(node, url_path): diff --git a/content/docs/api/environment/virtualpathresolver/contents.lr b/content/docs/api/environment/virtualpathresolver/contents.lr new file mode 100644 index 00000000..b22b250c --- /dev/null +++ b/content/docs/api/environment/virtualpathresolver/contents.lr @@ -0,0 +1,63 @@ +title: virtualpathresolver +--- +signature: prefix +--- +summary: Registers a virtual path resolver with the environment. +--- +type: method +--- +version_added: 2.0 +--- +body: + +When implementing [Virtual Source Objects :ref](../../db/obj/) it's important +that you can locate them. While virtual sources do not appear as children of +pages they can be navigated to through the database pad. This is achieved +through custom virtual path resolvers. + +Each source object needs to be a unique prefix which identifies all instances +of it. For instance if you want to implement a special page (like a feed) +that would exist for specific pages, then you can register the virtual path +prefix `feed` with your plugin. Though you should use more descriptive names +for your plugins as these paths are shared. + +If a user then resolves the page `/my-page@feed` it would invoke your URL +resolver with the record `my-page` and an empty list (rest of the path +segments). If they would request `/my-page@feed/recent` then it would +pass `['recent']` as path segments. + +## Example + +Here an example that would implement a virtual source for feeds and an +associated virtual path resolver: + +```python +from lektor.sourceobj import VirtualSourceObject +from lektor.utils import build_url + +class Feed(VirtualSourceObject): + + def __init__(self, record, version='default'): + VirtualSourceObject.__init__(self, record) + self.version = version + + @property + def path(self): + return '%s@feed%s' % ( + self.record.path, + '/' + self.version if self.version != 'default' else '', + ) + + @property + def url_path(self): + return build_url([self.record.url_path, 'feed.xml']) + + +def on_setup_env(self, **extra): + @self.env.virtualpathresolver('feed') + def resolve_virtual_path(record, pieces): + if not pieces: + return Feed(record) + elif pieces == ['recent']: + return Feed(record, version='recent') +``` diff --git a/content/docs/api/utils/build-url/contents.lr b/content/docs/api/utils/build-url/contents.lr new file mode 100644 index 00000000..3488fc90 --- /dev/null +++ b/content/docs/api/utils/build-url/contents.lr @@ -0,0 +1,32 @@ +title: build_url +--- +module: lektor.utils +--- +signature: pieces, trailing_slash=None +--- +summary: Helper function that assists in building URL paths. +--- +type: function +--- +version_added: 2.0 +--- +body: + +This function assists in creating URL paths from individual segments. This +is particularly useful for building virtual source objects. It takes a bunch +of path segments and returns an absolute path. The default behavior is to +guess the trailing slash based on the presence of a dot in the last path +segment. If you want to override the detection you can explicitly pass +`True` to enforce a trailing slash or `False` to avoid it. + +## Example + +```pycon +>>> from lektor.utils import build_url +>>> build_url(['foo', 42]) +u'/foo/42/' +>>> build_url(['foo', None, 23]) +u'/foo/23/' +>>> build_url(['foo', 'hello.html']) +u'/foo/hello.html' +``` diff --git a/content/docs/api/utils/join-path/contents.lr b/content/docs/api/utils/join-path/contents.lr new file mode 100644 index 00000000..b17422f4 --- /dev/null +++ b/content/docs/api/utils/join-path/contents.lr @@ -0,0 +1,45 @@ +title: join_path +--- +module: lektor.utils +--- +signature: a, b +--- +summary: Joins two Lektor paths together correctly. +--- +template_var: +--- +type: function +--- +version_added: 2.0 +--- +body: + +Given two Lektor paths this joins them together with the rules that Lektor +set up for this. In particular this is important in the presence of +virtual paths which have their own join semantics. + +## Example + +```pycon +>>> from lektor.utils import join_path +>>> join_path('/blog', 'hello-world') +'/blog/hello-world' +>>> join_path('/blog', '@archive') +'/blog@archive' +>>> join_path('/blog@archive', '2015') +'/blog@archive/2015' +>>> join_path('/blog@archive/2015', '..') +'/blog@archive' +>>> join_path('/blog@archive', '..') +'/blog' +``` + +Special note should be taken for the numeric virtual path of paginations. It +is considered to be on the same level as the actual record: + +```pycon +>>> join_path('/blog@2', '..') +'/' +>>> join_path('/blog@2', '.') +'/blog@2' +``` diff --git a/content/docs/api/utils/parse-path/contents.lr b/content/docs/api/utils/parse-path/contents.lr new file mode 100644 index 00000000..1d69ee2f --- /dev/null +++ b/content/docs/api/utils/parse-path/contents.lr @@ -0,0 +1,25 @@ +title: parse_path +--- +module: lektor.utils +--- +summary: Parses a path into components. +--- +type: function +--- +version_added: 2.0 +--- +body: + +This function parses a path into the individual components it's made from. +The path is always assumed to be absolute and made absolute if it's not yet +so. The root path is the empty list. + +## Example + +```pycon +>>> from lektor.utils import parse_path +>>> parse_path('/foo/bar') +['foo', 'bar'] +>>> parse_path('/') +[] +``` diff --git a/content/docs/content/alts/contents.lr b/content/docs/content/alts/contents.lr index 15933242..ad5bdff0 100644 --- a/content/docs/content/alts/contents.lr +++ b/content/docs/content/alts/contents.lr @@ -72,3 +72,11 @@ targeted, different files will be used. This table visualizes this: | | ✓ | | fr | contents+fr.lr | | | ✓ | en | contents+en.lr | | | ✓ | fr | *missing* + +## Alternatives and Paths + +Alternatives have a special behavior with regards to paths. They alternative +code does not exist in the path! This can be confusing at first, but has the +advantage that they automatically work in most places as the paths are the +same for differnent alternatives. For more information see +[Alternatives and Paths :ref](../paths/#alternatives-and-paths). diff --git a/content/docs/content/paths/contents.lr b/content/docs/content/paths/contents.lr new file mode 100644 index 00000000..92987ddf --- /dev/null +++ b/content/docs/content/paths/contents.lr @@ -0,0 +1,96 @@ +title: Paths +--- +summary: An explanation about how paths in Lektor work. +--- +body: + +!! This refers mostly to Lektor 2.0. If you are using an older version of +Lektor the virtual path feature is not implemented yet and a lot of this just +does not apply. + +Lektor attempts to map the paths from the content folder as closely as possible +to the final URLs (at least in the default configuration). There are various +ways in which this can be customized (see [URLs and Slugs :ref](../urls/)) and +there are situations in which Lektor needs to render out content that does not +actually directly correspond to a path on the file system. + +Here we try to explain how the path system in Lektor works and what it means. + +## What is a Path + +A path in Lektor is a string that uniquely identifies a [Source Object +:ref](../../api/db/obj/). For the most part these directly point to +`content.lr` files or attachments therein. These paths always use forward +slashes, no matter on which platform Lektor runs. So for instance if you +have a file named `projects/my-project/contents.lr` then the path for this +record will be `/projects/my-project`. Thereby it does not matter if the +slug or final URL was changed, the path is always the same. + +Lektor uses paths to refer to records by default in all cases. This means that +if you use the [url filter :ref](../../api/templates/filters/url/) for instance +it will operate on paths and not URLs! + +But what about sources that do not directly correspond to something on the +file system? This is where virtual paths come in. Virtual paths are +separated from physical paths with an at (`@`) sign. Virtual paths are always +attached to a record and point to things that are not actually coming from the +content folder but something else. + +Virtual paths are for instance used to refer to paginated pages or to some +resources that plugins add. + +## Virtual Paths + +Virtual paths are added behind physical paths and are separated by an at +sign (`@`). The only virtual path that is supported by Lektor out of the box +is the special numeric virtual path which can be used to refer to specific +pages for a pagination. For instance `/blog@1` refers to the first page of +the `blog` page, `/blog@2` to the second etc. There are however plugins that +add virtual paths to refer to their own resources. For instance a plugin can +register `/blog@feed` to refer to a RSS/Atom feed for the blog. + +## Relative Paths + +Now that we talked a bit about paths, we should probably cover how relative +paths work. Relative paths work similar to how you expect them to work on +most operating systems but they can operate on the virtual as well as the +physical path. There is also some special behavior with regards to the numeric +virtual path for pagination. + +For the most part `.` refers to the same page and `..` refers to the page one +level up. If you use a path that just contains of a virtual path, then it's +attached to the current page to replace the active virtual path. + +| Current | Relative | Result +| --------------- | ------------ | ---------------- +| `/blog` | `..` | `/` +| `/blog` | `.` | `/blog` +| `/blog/post` | `..` | `/blog` +| `/blog` | `@2` | `/blog@2` +| `/blog@2` | `@1` | `/blog@1` +| `/blog@feed` | `recent` | `/blog@feed/recent` +| `/blog@feed` | `..` | `/blog@feed` + +However! The numeric path is special with regards because it's considered to +belong to the current page: + +| Current | Relative | Result +| --------------- | ------------ | ---------------- +| `/blog@2` | `.` | `/blog@2` +| `/blog@2` | `..` | `/` + +## Alternatives and Paths + +If you have used Lektor a bit you might be wondering how [Alternatives +:ref](../alts/) work with paths. The answer might be surprising but it's +basically that they don't really work with paths. Alternatives are +implemented on a level higher than paths. If you have a page that exists +in both German and English alternative then they will have the same path. +The alternative code is supplied separately. + +This is done so that you can later start introducing alternatives to sites +that were never aware of them without having to go everywhere and start +passing alternative information around. While there are indeed some places +where you might have to perform some changes (especially if you perform +manual queries in the templates) for the most part adding alternatives to an +existing site later is a trivial matter. diff --git a/packages/blog-archive/lektor_blog_archive.py b/packages/blog-archive/lektor_blog_archive.py index 38c06d23..0b00f858 100644 --- a/packages/blog-archive/lektor_blog_archive.py +++ b/packages/blog-archive/lektor_blog_archive.py @@ -1,72 +1,45 @@ # -*- 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.context import get_ctx - - -def get_path_segments(str): - pieces = str.split('/') - if pieces == ['']: - return [] - return pieces - - -def push_path(pieces, item): - if item: - pieces.append(unicode(item)) +from lektor.utils import build_url, parse_path class BlogArchive(VirtualSourceObject): - def __init__(self, parent, plugin, items=None, year=None, month=None): - VirtualSourceObject.__init__(self, parent) + def __init__(self, record, plugin, items=None): + VirtualSourceObject.__init__(self, record) self.plugin = plugin self._items = items - self.year = year - self.month = month @property - def date(self): - if self.year is None: - raise AttributeError() - return date(self.year, self.month or 1, 1) + def path(self): + return self.record.path + '@blog-archive' + + template_name = 'blog-archive/index.html' @property - def year_archive(self): - if self.year is None: - raise AttributeError() - if self.month is None: - return self - return BlogArchive(self.parent, self.plugin, year=self.year) - - @property - def archive_index(self): - if self.year is None: - return self - return BlogArchive(self.parent, self.plugin) + def parent(self): + return self.record @cached_property def year_archives(self): - if self.year is not None: - return [] years = set() - for item in self.parent.children: + for item in self.record.children: pub_date = self.plugin.get_pub_date(item) if pub_date: years.add(pub_date.year) - return [BlogArchive(self.parent, self.plugin, - year=year) for year in sorted(years)] + return [BlogYearArchive(self.record, self.plugin, + year=year) for year in sorted(years)] @property def items(self): - if self.year is None: - return [] if self._items is not None: return self._items rv = list(self._iter_items()) @@ -74,13 +47,7 @@ class BlogArchive(VirtualSourceObject): return rv def _iter_items(self): - for item in self.parent.children: - pub_date = self.plugin.get_pub_date(item) - if pub_date is None: - continue - if pub_date.year == self.year and \ - (self.month is None or pub_date.month == self.month): - yield item + return iter(()) @property def has_any_items(self): @@ -97,34 +64,87 @@ class BlogArchive(VirtualSourceObject): pub_date = self.plugin.get_pub_date(item) months.setdefault(date(pub_date.year, pub_date.month, 1), []).append(item) - return [(BlogArchive(self.parent, self.plugin, - year=d.year, month=d.month), i) + 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): - prefix = self.parent.url_path.strip('/') - pieces = [] - if prefix: - pieces.append(prefix) - if self.year is None: - push_path(pieces, self.plugin.get_archive_index_path()) - elif self.month is None: - push_path(pieces, self.plugin.get_month_archive_prefix()) - push_path(pieces, self.year) - else: - push_path(pieces, self.plugin.get_year_archive_prefix()) - push_path(pieces, self.year) - push_path(pieces, self.month) - return '/%s/' % '/'.join(pieces) + 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 template_name(self): - if self.year is None: - return 'blog-archive/index.html' - if self.month is None: - return 'blog-archive/year.html' - return 'blog-archive/month.html' + 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): @@ -150,77 +170,70 @@ class BlogArchivePlugin(Plugin): def get_blog_path(self): return self.get_config().get('blog_path', '/blog') - def get_archive_index_path(self): - return self.get_config().get('archive_path', 'archive').strip('/') - - def get_year_archive_prefix(self): - return self.get_config().get('year_archive_prefix', 'archive').strip('/') - - def get_month_archive_prefix(self): - return self.get_config().get('month_archive_prefix', 'archive').strip('/') + def get_url_path(self, name, default='archive'): + return parse_path(self.get_config().get(name, default)) def on_setup_env(self, **extra): - blog_path = self.get_blog_path() self.env.add_build_program(BlogArchive, BlogArchiveBuildProgram) - def get_blog_archive(): - pad = get_ctx().pad - blog = pad.get(blog_path) - if blog is not None: - return BlogArchive(blog, self) - self.env.jinja_env.globals['get_blog_archive'] = get_blog_archive + @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_resolver(node, url_path): - if node.path != blog_path: + def archive_urlresolver(node, url_path): + if node.path != self.get_blog_path(): return - archive_index = get_path_segments(self.get_archive_index_path()) + archive_index = self.get_url_path('archive_path') if url_path == archive_index: return BlogArchive(node, self) - year_prefix = get_path_segments(self.get_year_archive_prefix()) - month_prefix = get_path_segments(self.get_month_archive_prefix()) - - year = None - month = None - + 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)]) - elif (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()): + 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]) - else: - return None - - rv = BlogArchive(node, self, year=year, month=month) - if rv.has_any_items: - return rv + 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 != blog_path: + if source.path != self.get_blog_path(): return - blog = source - years = {} months = {} - for post in blog.children: + 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(blog, self) + yield BlogArchive(source, self) for year, items in sorted(years.items()): - yield BlogArchive(blog, self, year=year, items=items) + yield BlogYearArchive(source, self, year=year, items=items) for (year, month), items in sorted(months.items()): - yield BlogArchive(blog, self, year=year, month=month, - items=items) + yield BlogMonthArchive(source, self, year=year, month=month, + items=items) diff --git a/templates/blog-archive/month.html b/templates/blog-archive/month.html index 3f4247ec..bd5301f0 100644 --- a/templates/blog-archive/month.html +++ b/templates/blog-archive/month.html @@ -3,7 +3,7 @@ {% block blog_body %}

The Transcript, {{ this.date|dateformat('MMMM yyyy') }}

- « Back to {{ this.year }} + « Back to {{ this.year }}