From 2c434ebac1c64ac2da85cdeeadeaf2c3bd5307cc Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 3 Jan 2013 13:54:56 -0500 Subject: [PATCH 1/4] contents: Add rich comparisons to URLWrapper for easy sorting There have been earlier attempts to sort categories and authors [1,2,3], but they either sorted based on the object id [3], or only sorted the main author and categories list. This patch uses rich comparisons (keyed off URLWrapper.name, but easily adjustable in subclasses) to make the objects sortable without specifying a key for each sort. For example, now {% for tag, articles in tags|sort %} works as expected in a Jinja template. The functools.total_ordering decorator fills in the missing rich comparisons [4,5]. [1]: 877d454c8fa979343d4881a00977d9ac8786f178 [2]: 7f36e0ed20745c73886d96d4af303b0d858b8a53 [3]: d0ec18f4dbd623c55bdf4928ee7c363f699ef696 [4]: http://docs.python.org/2/library/functools.html#functools.total_ordering [5]: http://docs.python.org/3/library/functools.html#functools.total_ordering --- pelican/contents.py | 16 +++++++++++++++- pelican/generators.py | 3 +-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 4655d4cc..bd257ad8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -243,7 +243,9 @@ class Article(Page): class Quote(Page): base_properties = ('author', 'date') + @python_2_unicode_compatible +@functools.total_ordering class URLWrapper(object): def __init__(self, name, settings): self.name = name @@ -256,8 +258,20 @@ class URLWrapper(object): def __hash__(self): return hash(self.name) + def _key(self): + return self.name + + def _normalize_key(self, key): + return six.text_type(key) + def __eq__(self, other): - return self.name == other + return self._key() == self._normalize_key(other) + + def __ne__(self, other): + return self._key() != self._normalize_key(other) + + def __lt__(self, other): + return self._key() < self._normalize_key(other) def __str__(self): return self.name diff --git a/pelican/generators.py b/pelican/generators.py index ce102a31..49b6bc1d 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -416,11 +416,10 @@ class ArticlesGenerator(Generator): # order the categories per name self.categories = list(self.categories.items()) self.categories.sort( - key=lambda item: item[0].name, reverse=self.settings['REVERSE_CATEGORY_ORDER']) self.authors = list(self.authors.items()) - self.authors.sort(key=lambda item: item[0].name) + self.authors.sort() self._update_context(('articles', 'dates', 'tags', 'categories', 'tag_cloud', 'authors', 'related_posts')) From 656b5150ff26988d71d41fb32f2f75aeca665617 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Jan 2013 16:32:11 -0500 Subject: [PATCH 2/4] docs/themes.rst: Document URLWrapper sorting for use in Jinja templates --- docs/themes.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/themes.rst b/docs/themes.rst index 664b4466..19fd9274 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -68,6 +68,19 @@ categories A list of (category, articles) tuples, containing pages The list of pages ============= =================================================== +Sorting +------- + +URL wrappers (currently categories, tags, and authors), have +comparison methods that allow them to be easily sorted by name: + + {% for tag, articles in tags|sort %} + +If you want to sort based on different criteria, `Jinja's sort +command`__ has a number of options. + +__ http://jinja.pocoo.org/docs/templates/#sort + index.html ---------- From bebb94c15be1ae7ff7ec241f6e1de5aa18556654 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Jan 2013 22:50:58 -0500 Subject: [PATCH 3/4] test_contents.py: Add URLWrapper comparison tests The name switch between wrapper_a and wrapper_b ensures that we're not secretly comparing some other field (e.g. id(object)). --- tests/test_contents.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/test_contents.py b/tests/test_contents.py index eb7b6514..9b5673bd 100644 --- a/tests/test_contents.py +++ b/tests/test_contents.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from .support import unittest -from pelican.contents import Page, Article +from pelican.contents import Page, Article, URLWrapper from pelican.settings import _DEFAULT_CONFIG from pelican.utils import truncate_html_words from pelican.signals import content_object_init @@ -187,3 +188,31 @@ class TestArticle(TestPage): article_kwargs['metadata']['template'] = 'custom' custom_article = Article(**article_kwargs) self.assertEqual('custom', custom_article.template) + + +class TestURLWrapper(unittest.TestCase): + def test_comparisons(self): + """URLWrappers are sorted by name + """ + wrapper_a = URLWrapper(name='first', settings={}) + wrapper_b = URLWrapper(name='last', settings={}) + self.assertFalse(wrapper_a > wrapper_b) + self.assertFalse(wrapper_a >= wrapper_b) + self.assertFalse(wrapper_a == wrapper_b) + self.assertTrue(wrapper_a != wrapper_b) + self.assertTrue(wrapper_a <= wrapper_b) + self.assertTrue(wrapper_a < wrapper_b) + wrapper_b.name = 'first' + self.assertFalse(wrapper_a > wrapper_b) + self.assertTrue(wrapper_a >= wrapper_b) + self.assertTrue(wrapper_a == wrapper_b) + self.assertFalse(wrapper_a != wrapper_b) + self.assertTrue(wrapper_a <= wrapper_b) + self.assertFalse(wrapper_a < wrapper_b) + wrapper_a.name = 'last' + self.assertTrue(wrapper_a > wrapper_b) + self.assertTrue(wrapper_a >= wrapper_b) + self.assertFalse(wrapper_a == wrapper_b) + self.assertTrue(wrapper_a != wrapper_b) + self.assertFalse(wrapper_a <= wrapper_b) + self.assertFalse(wrapper_a < wrapper_b) From d2a221c8998dff7adb4baa75c4c0f2953187cb7f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 17 Jan 2013 09:49:03 -0500 Subject: [PATCH 4/4] contents: Encode Unicode locales for Python 2 in Page.__init__ Python 2.7 chokes on Unicode locales: $ python2.7 >>> import locale >>> locale.setlocale(locale.LC_ALL, u'ja_JP.utf8') Traceback (most recent call last): ... ValueError: too many values to unpack With the addition of: from __future__ import unicode_literals to tests/test_contents.py in: commit bebb94c15be1ae7ff7ec241f6e1de5aa18556654 Author: W. Trevor King Date: Tue Jan 15 22:50:58 2013 -0500 test_contents.py: Add URLWrapper comparison tests the locale strings in TestPage.test_datetime are interpreted as Unicode. Rather than fixing the encoding there, this patch updates Page to handle Unicode locales automatically. --- pelican/contents.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pelican/contents.py b/pelican/contents.py index bd257ad8..dc38e32f 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -8,6 +8,7 @@ import logging import functools import os import re +import sys from datetime import datetime from sys import platform, stdin @@ -86,7 +87,10 @@ class Page(object): self.date_format = settings['DEFAULT_DATE_FORMAT'] if isinstance(self.date_format, tuple): - locale.setlocale(locale.LC_ALL, self.date_format[0]) + locale_string = self.date_format[0] + if sys.version_info < (3, ) and isinstance(locale_string, six.text_type): + locale_string = locale_string.encode('ascii') + locale.setlocale(locale.LC_ALL, locale_string) self.date_format = self.date_format[1] if hasattr(self, 'date'):