Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fix #1889 -- support dates in post_list
Signed-off-by: Chris Warrick <kwpolska@gmail.com>
  • Loading branch information
Kwpolska committed Jul 13, 2016
1 parent 451c51f commit dd02d68
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -4,6 +4,8 @@ New in master
Features
--------

* Support ``date`` filtering in the post list directive
(Issue #1889)
* Added Albanian translation by Vango Stavro
* Added ``post-(type)`` class to ``story.tmpl`` (uses the ``type``
meta field, defaults to ``post-text`` — same behavior as posts)
Expand Down
9 changes: 9 additions & 0 deletions docs/manual.txt
Expand Up @@ -1964,6 +1964,15 @@ The following options are recognized:
Sort post list by one of each post's attributes, usually ``title`` or a
custom ``priority``. Defaults to None (chronological sorting).

* ``date``: string
Show posts that match date range specified by this option. Format:

* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input

* ``tags`` : string [, string...]
Filter posts to show only posts having at least one of the ``tags``.
Defaults to None.
Expand Down
2 changes: 2 additions & 0 deletions nikola/packages/README.md
Expand Up @@ -3,3 +3,5 @@ We ship some third-party things with Nikola. They live here, along with their l
Packages:

* tzlocal by Lennart Regebro, CC0 license (modified)
* datecond by Chris Warrick (Nikola contributor), 3-clause BSD license
(modified)
85 changes: 85 additions & 0 deletions nikola/packages/datecond/__init__.py
@@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Date Conditionals (datecond)
# Version 0.1.2
# Copyright © 2015-2016, Chris Warrick.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the author of this software nor the names of
# contributors to this software may be used to endorse or promote
# products derived from this software without specific prior written
# consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Date range parser."""

from __future__ import print_function, unicode_literals
import dateutil.parser
import re
import operator


__all__ = ('date_in_range',)
CLAUSE = re.compile('(year|month|day|hour|minute|second|weekday|isoweekday)?'
' ?(==|!=|<=|>=|<|>) ?(.*)')
OPERATORS = {
'==': operator.eq,
'!=': operator.ne,
'<=': operator.le,
'>=': operator.ge,
'<': operator.lt,
'>': operator.gt,
}


def date_in_range(date_range, date, debug=True):
"""Check if date is in the range specified.
Format:
* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday
or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input
"""
out = True

for item in date_range.split(','):
attribute, comparison_operator, value = CLAUSE.match(
item.strip()).groups()
if attribute in ('weekday', 'isoweekday'):
left = getattr(date, attribute)()
right = int(value)
elif attribute:
left = getattr(date, attribute)
right = int(value)
else:
left = date
right = dateutil.parser.parse(value)
if debug: # pragma: no cover
print(" <{0} {1} {2}>".format(left, comparison_operator, right))
out = out and OPERATORS[comparison_operator](left, right)
return out
26 changes: 22 additions & 4 deletions nikola/plugins/compile/rest/post_list.py
Expand Up @@ -37,6 +37,7 @@

from nikola import utils
from nikola.plugin_categories import RestExtension
from nikola.packages.datecond import date_in_range

# WARNING: the directive name is post-list
# (with a DASH instead of an UNDERSCORE)
Expand Down Expand Up @@ -86,10 +87,19 @@ class PostList(Directive):
Reverse the order of the post-list.
Defaults is to not reverse the order of posts.
``sort``: string
``sort`` : string
Sort post list by one of each post's attributes, usually ``title`` or a
custom ``priority``. Defaults to None (chronological sorting).
``date`` : string
Show posts that match date range specified by this option. Format:
* comma-separated clauses (AND)
* clause: attribute comparison_operator value (spaces optional)
* attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime
* comparison_operator: == != <= >= < >
* value: integer or dateutil-compatible date input
``tags`` : string [, string...]
Filter posts to show only posts having at least one of the ``tags``.
Defaults to None.
Expand Down Expand Up @@ -136,6 +146,7 @@ class PostList(Directive):
'lang': directives.unchanged,
'template': directives.path,
'id': directives.unchanged,
'date': directives.unchanged,
}

def run(self):
Expand All @@ -154,17 +165,21 @@ def run(self):
lang = self.options.get('lang', utils.LocaleBorg().current_lang)
template = self.options.get('template', 'post_list_directive.tmpl')
sort = self.options.get('sort')
date = self.options.get('date')

output = _do_post_list(start, stop, reverse, tags, categories, slugs, post_type,
show_all, lang, template, sort, state=self.state, site=self.site)
show_all, lang, template, sort, state=self.state, site=self.site, date=date)
self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE")
return [nodes.raw('', output, format='html')]
if output:
return [nodes.raw('', output, format='html')]
else:
return []


def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=None,
slugs=None, post_type='post', show_all=False, lang=None,
template='post_list_directive.tmpl', sort=None, id=None,
data=None, state=None, site=None):
data=None, state=None, site=None, date=None):
if lang is None:
lang = utils.LocaleBorg().current_lang
if site.invariant: # for testing purposes
Expand Down Expand Up @@ -213,6 +228,9 @@ def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=No
if sort:
filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC)

if date:
filtered_timeline = [p for p in filtered_timeline if date_in_range(date, p.date)]

for post in filtered_timeline[start:stop:step]:
if slugs:
cont = True
Expand Down

0 comments on commit dd02d68

Please sign in to comment.