Documented virtual paths
This commit is contained in:
parent
06ac62c715
commit
b3ff365a8a
|
@ -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/).
|
||||
|
|
|
@ -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 %}
|
||||
<a href="{{ this.record|url }}">go back to overview</a>
|
||||
{% endif %}
|
||||
```
|
|
@ -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') %}
|
||||
<p>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')
|
||||
<Page model=u'blog' path='/blog' page_num=3>
|
||||
```
|
||||
|
|
|
@ -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 %}
|
||||
</div>
|
||||
```
|
||||
|
||||
## 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
|
||||
<a href="{{ '@2'|url }}">Go to Page 2</a>
|
||||
```
|
||||
|
||||
Alternatively you can also use the `for_path()` function which returns the
|
||||
entire pagination for a page:
|
||||
|
||||
```html+jinja
|
||||
<a href="{{ this.pagination.for_page(2)|url }}">Go to Page 2</a>
|
||||
```
|
||||
|
|
|
@ -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
|
||||
<a href="{{ this.parent|url }}">Up to {{ this.parent.title }}</a>
|
||||
```
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
```
|
|
@ -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'
|
||||
```
|
|
@ -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'
|
||||
```
|
|
@ -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('/')
|
||||
[]
|
||||
```
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block blog_body %}
|
||||
<h1>The Transcript, {{ this.date|dateformat('MMMM yyyy') }}</h1>
|
||||
<p>
|
||||
<a href="{{ this.year_archive|url }}">« Back to {{ this.year }}</a>
|
||||
<a href="{{ '..'|url }}">« Back to {{ this.year }}</a>
|
||||
<ul>
|
||||
{% for post in this.items %}
|
||||
<li><a href="{{ post|url }}">{{ post.title }}</a> by {{ post.author }} on {{ post.pub_date|dateformat }}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block blog_body %}
|
||||
<h1>The Transcript in {{ this.year }}</h1>
|
||||
<p>
|
||||
<a href="{{ this.archive_index|url }}">« Back to the archive</a>
|
||||
<a href="{{ '..'|url }}">« Back to the archive</a>
|
||||
<ul>
|
||||
{% for archive, items in this.items_by_months %}
|
||||
<li><a href="{{ archive|url }}">{{ archive.date|dateformat('MMMM') }}</a>:
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
</ul>
|
||||
<h3>Missed a Post?</h3>
|
||||
<ul>
|
||||
{% set archive = get_blog_archive() %}
|
||||
<li><a href="{{ archive|url }}">Blog Archives</a>
|
||||
<li><a href="{{ '$blog-archive'|url }}">Blog Archives</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue