Skip to content

Commit

Permalink
Merge pull request #3044 from getnikola/make-post-list-independent
Browse files Browse the repository at this point in the history
Make post list shortcode independent of the reSt directive
  • Loading branch information
Kwpolska committed Apr 16, 2018
2 parents c9b3da7 + e5a9e64 commit 9487a25
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 217 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -51,6 +51,8 @@ Features
Bugfixes
--------

* Put post_list shortcode in its own plugin and make the reSt
directive depend on it.
* Don’t silence syntax errors and other exceptions that occur while
reading metadata
* Use documented dateutil API for time zone list (Issue #3006)
Expand Down
4 changes: 2 additions & 2 deletions nikola/plugins/compile/rest/post_list.plugin
Expand Up @@ -8,7 +8,7 @@ PluginCategory = CompilerExtension

[Documentation]
author = Udo Spallek
version = 0.1
version = 0.2
website = https://getnikola.com/
description = Includes a list of posts with tag and slide based filters.
description = Includes a list of posts with tag and slice based filters.

242 changes: 27 additions & 215 deletions nikola/plugins/compile/rest/post_list.py
Expand Up @@ -23,21 +23,15 @@
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Post list directive for reStructuredText."""


import os
import uuid
import natsort
import operator

from docutils import nodes
from docutils.parsers.rst import Directive, directives

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

# from nikola.plugins.shortcode.post_list import _do_post_list

# WARNING: the directive name is post-list
# (with a DASH instead of an UNDERSCORE)
Expand All @@ -51,91 +45,13 @@ class Plugin(RestExtension):
def set_site(self, site):
"""Set Nikola site."""
self.site = site
self.site.register_shortcode('post-list', _do_post_list)
directives.register_directive('post-list', PostList)
PostList.site = site
directives.register_directive('post-list', PostListDirective)
PostListDirective.site = site
return super(Plugin, self).set_site(site)


class PostList(Directive):
"""Provide a reStructuredText directive to create a list of posts.
Post List
=========
:Directive Arguments: None.
:Directive Options: lang, start, stop, reverse, sort, date, tags, categories, sections, slugs, post_type, template, id
:Directive Content: None.
The posts appearing in the list can be filtered by options.
*List slicing* is provided with the *start*, *stop* and *reverse* options.
The following not required options are recognized:
``start`` : integer
The index of the first post to show.
A negative value like ``-3`` will show the *last* three posts in the
post-list.
Defaults to None.
``stop`` : integer
The index of the last post to show.
A value negative value like ``-1`` will show every post, but not the
*last* in the post-list.
Defaults to None.
``reverse`` : flag
Reverse the order of the post-list.
Defaults is to not reverse the order of posts.
``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, 'now' or dateutil-compatible date input
``tags`` : string [, string...]
Filter posts to show only posts having at least one of the ``tags``.
Defaults to None.
``require_all_tags`` : flag
Change tag filter behaviour to show only posts that have all specified ``tags``.
Defaults to False.
``categories`` : string [, string...]
Filter posts to show only posts having one of the ``categories``.
Defaults to None.
``sections`` : string [, string...]
Filter posts to show only posts having one of the ``sections``.
Defaults to None.
``slugs`` : string [, string...]
Filter posts to show only posts having at least one of the ``slugs``.
Defaults to None.
``post_type`` (or ``type``) : string
Show only ``posts``, ``pages`` or ``all``.
Replaces ``all``. Defaults to ``posts``.
``lang`` : string
The language of post *titles* and *links*.
Defaults to default language.
``template`` : string
The name of an alternative template to render the post-list.
Defaults to ``post_list_directive.tmpl``
``id`` : string
A manual id for the post list.
Defaults to a random name composed by 'post_list_' + uuid.uuid4().hex.
"""
class PostListDirective(Directive):
"""Provide a reStructuredText directive to create a list of posts."""

option_spec = {
'start': int,
Expand Down Expand Up @@ -173,134 +89,30 @@ def run(self):
date = self.options.get('date')
filename = self.state.document.settings._nikola_source_path

output, deps = _do_post_list(start, stop, reverse, tags, require_all_tags, categories, sections, slugs, post_type, type,
lang, template, sort, state=self.state, site=self.site, date=date, filename=filename)
self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE")
output, deps = self.site.plugin_manager.getPluginByName(
'post_list', 'ShortcodePlugin').plugin_object.handler(
start,
stop,
reverse,
tags,
require_all_tags,
categories,
sections,
slugs,
post_type,
type,
lang,
template,
sort,
state=self.state,
site=self.site,
date=date,
filename=filename)
self.state.document.settings.record_dependencies.add(
"####MAGIC####TIMELINE")
for d in deps:
self.state.document.settings.record_dependencies.add(d)
if output:
return [nodes.raw('', output, format='html')]
else:
return []


def _do_post_list(start=None, stop=None, reverse=False, tags=None, require_all_tags=False, categories=None,
sections=None, slugs=None, post_type='post', type=False,
lang=None, template='post_list_directive.tmpl', sort=None,
id=None, data=None, state=None, site=None, date=None, filename=None, post=None):
if lang is None:
lang = utils.LocaleBorg().current_lang
if site.invariant: # for testing purposes
post_list_id = id or 'post_list_' + 'fixedvaluethatisnotauuid'
else:
post_list_id = id or 'post_list_' + uuid.uuid4().hex

# Get post from filename if available
if filename:
self_post = site.post_per_input_file.get(filename)
else:
self_post = None

if self_post:
self_post.register_depfile("####MAGIC####TIMELINE", lang=lang)

# If we get strings for start/stop, make them integers
if start is not None:
start = int(start)
if stop is not None:
stop = int(stop)

# Parse tags/categories/sections/slugs (input is strings)
categories = [c.strip().lower() for c in categories.split(',')] if categories else []
sections = [s.strip().lower() for s in sections.split(',')] if sections else []
slugs = [s.strip() for s in slugs.split(',')] if slugs else []

filtered_timeline = []
posts = []
step = -1 if reverse is None else None

if type is not False:
post_type = type

if post_type == 'page' or post_type == 'pages':
timeline = [p for p in site.timeline if not p.use_in_feeds]
elif post_type == 'all':
timeline = [p for p in site.timeline]
else: # post
timeline = [p for p in site.timeline if p.use_in_feeds]

# self_post should be removed from timeline because this is redundant
timeline = [p for p in timeline if p.source_path != filename]

if categories:
timeline = [p for p in timeline if p.meta('category', lang=lang).lower() in categories]

if sections:
timeline = [p for p in timeline if p.section_name(lang).lower() in sections]

if tags:
tags = {t.strip().lower() for t in tags.split(',')}
if require_all_tags:
compare = set.issubset
else:
compare = operator.and_
for post in timeline:
post_tags = {t.lower() for t in post.tags}
if compare(tags, post_tags):
filtered_timeline.append(post)
else:
filtered_timeline = timeline

if sort:
filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC)

if date:
_now = utils.current_time()
filtered_timeline = [p for p in filtered_timeline if date_in_range(utils.html_unescape(date), p.date, now=_now)]

for post in filtered_timeline[start:stop:step]:
if slugs:
cont = True
for slug in slugs:
if slug == post.meta('slug'):
cont = False

if cont:
continue

bp = post.translated_base_path(lang)
if os.path.exists(bp) and state:
state.document.settings.record_dependencies.add(bp)
elif os.path.exists(bp) and self_post:
self_post.register_depfile(bp, lang=lang)

posts += [post]

if not posts:
return '', []

template_deps = site.template_system.template_deps(template)
if state:
# Register template as a dependency (Issue #2391)
for d in template_deps:
state.document.settings.record_dependencies.add(d)
elif self_post:
for d in template_deps:
self_post.register_depfile(d, lang=lang)

template_data = {
'lang': lang,
'posts': posts,
# Need to provide str, not TranslatableSetting (Issue #2104)
'date_format': site.GLOBAL_CONTEXT.get('date_format')[lang],
'post_list_id': post_list_id,
'messages': site.MESSAGES,
'_link': site.link,
}
output = site.template_system.render_template(
template, None, template_data)
return output, template_deps


# Request file name from shortcode (Issue #2412)
_do_post_list.nikola_shortcode_pass_filename = True
13 changes: 13 additions & 0 deletions nikola/plugins/shortcode/post_list.plugin
@@ -0,0 +1,13 @@
[Core]
name = post_list
module = post_list

[Nikola]
PluginCategory = Shortcode

[Documentation]
author = Udo Spallek
version = 0.2
website = https://getnikola.com/
description = Includes a list of posts with tag and slice based filters.

0 comments on commit 9487a25

Please sign in to comment.