| @@ -0,0 +1,106 @@ | |||||
| """ | |||||
| Contains classes and utilities related to sortin | |||||
| resources and nodes in hyde. | |||||
| """ | |||||
| import re | |||||
| from hyde.plugin import Plugin | |||||
| from hyde.site import Node, Resource | |||||
| from functools import partial | |||||
| from itertools import ifilter | |||||
| from operator import attrgetter | |||||
| import logging | |||||
| from logging import NullHandler | |||||
| logger = logging.getLogger('hyde.engine') | |||||
| logger.addHandler(NullHandler()) | |||||
| def filter_method(item, settings=None): | |||||
| """ | |||||
| Returns true if all the filters in the | |||||
| given settings evaluate to True. | |||||
| """ | |||||
| all_match = True | |||||
| if settings and hasattr(settings, 'filters'): | |||||
| filters = settings.filters | |||||
| for field, value in filters.__dict__.items(): | |||||
| try: | |||||
| res = attrgetter(field)(item) | |||||
| except: | |||||
| res = None | |||||
| if res != value: | |||||
| all_match = False | |||||
| break | |||||
| return all_match | |||||
| def sort_method(node, settings=None): | |||||
| """ | |||||
| Sorts the resources in the given node based on the | |||||
| given settings. | |||||
| """ | |||||
| attr = 'name' | |||||
| if settings and hasattr(settings, 'attr') and settings.attr: | |||||
| attr = settings.attr | |||||
| filter_ = partial(filter_method, settings=settings) | |||||
| resources = ifilter(filter_, node.walk_resources()) | |||||
| return sorted(resources, key=attrgetter(attr)) | |||||
| def make_method(method_name, method_): | |||||
| def method__(self): | |||||
| return method_(self) | |||||
| method__.__name__ = method_name | |||||
| return method__ | |||||
| def add_method(obj, method_name, method_, settings): | |||||
| m = make_method(method_name, partial(method_, settings=settings)) | |||||
| setattr(obj, method_name, m) | |||||
| class SorterPlugin(Plugin): | |||||
| """ | |||||
| Sorter plugin for hyde. Adds the ability to do | |||||
| sophisticated sorting by expanding the site objects | |||||
| to support prebuilt sorting methods. These methods | |||||
| can be used in the templates directly. | |||||
| Configuration example | |||||
| --------------------- | |||||
| #yaml | |||||
| sorter: | |||||
| kind: | |||||
| # Sorts by this attribute name | |||||
| # Uses `attrgetter` on the resource object | |||||
| attr: source_file.kind | |||||
| # The filters to be used before sorting | |||||
| # This can be used to remove all the items | |||||
| # that do not apply. For example, | |||||
| # filtering non html content | |||||
| filters: | |||||
| source_file.kind: html | |||||
| is_processable: True | |||||
| meta.is_listable: True | |||||
| """ | |||||
| def __init__(self, site): | |||||
| super(SorterPlugin, self).__init__(site) | |||||
| def begin_site(self): | |||||
| """ | |||||
| Initialize plugin. Add a sort and match method | |||||
| for every configuration mentioned in site settings | |||||
| """ | |||||
| config = self.site.config | |||||
| if not hasattr(config, 'sorter'): | |||||
| return | |||||
| for name, settings in config.sorter.__dict__.items(): | |||||
| logger.info("Adding sort methods for [%s]" % name) | |||||
| sort_method_name = 'walk_resources_sorted_by_%s' % name | |||||
| add_method(Node, sort_method_name, sort_method, settings) | |||||
| match_method_name = 'is_%s' % name | |||||
| add_method(Resource, match_method_name, filter_method, settings) | |||||
| @@ -169,35 +169,6 @@ class Node(Processable): | |||||
| for resource in node.resources: | for resource in node.resources: | ||||
| yield resource | yield resource | ||||
| def walk_resources_sorted(self, attr='name', reverse=False, default=None): | |||||
| """ | |||||
| Walks the resources in this hierarchy sorted by | |||||
| the given key. | |||||
| """ | |||||
| from operator import attrgetter | |||||
| def safe_attrgetter(*items): | |||||
| f = attrgetter(*items) | |||||
| def wrapper(obj): | |||||
| res = None | |||||
| try: | |||||
| res = f(obj) | |||||
| except: | |||||
| logger.error("Cannot get the requested items[%s]" | |||||
| " from the object [%s]" % | |||||
| (items, obj)) | |||||
| res = default | |||||
| return res | |||||
| return wrapper | |||||
| sorted_resources = sorted(self.walk_resources(), | |||||
| key=safe_attrgetter(attr), | |||||
| reverse=reverse) | |||||
| for resource in sorted_resources: | |||||
| yield resource | |||||
| @property | @property | ||||
| def relative_path(self): | def relative_path(self): | ||||
| """ | """ | ||||
| @@ -0,0 +1,116 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| """ | |||||
| Use nose | |||||
| `$ pip install nose` | |||||
| `$ nosetests` | |||||
| """ | |||||
| from hyde.ext.plugins.sorter import SorterPlugin | |||||
| from hyde.fs import File, Folder | |||||
| from hyde.generator import Generator | |||||
| from hyde.site import Site | |||||
| from hyde.model import Config, Expando | |||||
| import yaml | |||||
| TEST_SITE = File(__file__).parent.parent.child_folder('_test') | |||||
| class TestMeta(object): | |||||
| def setUp(self): | |||||
| TEST_SITE.make() | |||||
| TEST_SITE.parent.child_folder( | |||||
| 'sites/test_jinja').copy_contents_to(TEST_SITE) | |||||
| def tearDown(self): | |||||
| TEST_SITE.delete() | |||||
| def test_walk_resources_sorted(self): | |||||
| s = Site(TEST_SITE) | |||||
| s.load() | |||||
| s.config.plugins = ['hyde.ext.sorter.SorterPlugin'] | |||||
| s.config.sorter = Expando(dict(kind=dict(attr='source_file.kind'))) | |||||
| SorterPlugin(s).begin_site() | |||||
| assert hasattr(s.content, 'walk_resources_sorted_by_kind') | |||||
| expected = ["404.html", | |||||
| "about.html", | |||||
| "apple-touch-icon.png", | |||||
| "merry-christmas.html", | |||||
| "crossdomain.xml", | |||||
| "favicon.ico", | |||||
| "robots.txt", | |||||
| "site.css" | |||||
| ] | |||||
| pages = [page.name for page in | |||||
| s.content.walk_resources_sorted_by_kind()] | |||||
| assert pages == sorted(expected, key=lambda f: File(f).kind) | |||||
| def test_walk_resources_sorted_with_filters(self): | |||||
| s = Site(TEST_SITE) | |||||
| cfg = """ | |||||
| plugins: | |||||
| - hyde.ext.sorter.SorterPlugin | |||||
| sorter: | |||||
| kind2: | |||||
| filters: | |||||
| source_file.kind: html | |||||
| """ | |||||
| s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) | |||||
| s.load() | |||||
| SorterPlugin(s).begin_site() | |||||
| assert hasattr(s.content, 'walk_resources_sorted_by_kind2') | |||||
| expected = ["404.html", | |||||
| "about.html", | |||||
| "merry-christmas.html" | |||||
| ] | |||||
| pages = [page.name for page in s.content.walk_resources_sorted_by_kind2()] | |||||
| assert pages == sorted(expected) | |||||
| def test_walk_resources_sorted_using_generator(self): | |||||
| s = Site(TEST_SITE) | |||||
| cfg = """ | |||||
| meta: | |||||
| time: !!timestamp 2010-10-23 | |||||
| title: NahNahNah | |||||
| plugins: | |||||
| - hyde.ext.plugins.meta.MetaPlugin | |||||
| - hyde.ext.plugins.sorter.SorterPlugin | |||||
| sorter: | |||||
| time: | |||||
| attr: meta.time | |||||
| filters: | |||||
| source_file.kind: html | |||||
| """ | |||||
| s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) | |||||
| text = """ | |||||
| --- | |||||
| time: !!timestamp 2010-12-31 | |||||
| title: YayYayYay | |||||
| --- | |||||
| {% extends "base.html" %} | |||||
| {% block main %} | |||||
| {% set latest = site.content.walk_resources_sorted_by_time()|reverse|first %} | |||||
| <span class="latest">{{ latest.meta.title }}</span> | |||||
| {% endblock %} | |||||
| """ | |||||
| about2 = File(TEST_SITE.child('content/about2.html')) | |||||
| about2.write(text) | |||||
| gen = Generator(s) | |||||
| gen.generate_all() | |||||
| from pyquery import PyQuery | |||||
| target = File(Folder(s.config.deploy_root_path).child('about2.html')) | |||||
| text = target.read_all() | |||||
| q = PyQuery(text) | |||||
| assert q('span.latest').text() == 'YayYayYay' | |||||
| @@ -103,44 +103,6 @@ def test_walk_resources(): | |||||
| expected.sort() | expected.sort() | ||||
| assert pages == expected | assert pages == expected | ||||
| def test_walk_resources_sorted(): | |||||
| s = Site(TEST_SITE_ROOT) | |||||
| s.load() | |||||
| pages = [page.name for page in s.content.walk_resources_sorted(attr='name')] | |||||
| expected = ["404.html", | |||||
| "about.html", | |||||
| "apple-touch-icon.png", | |||||
| "merry-christmas.html", | |||||
| "crossdomain.xml", | |||||
| "favicon.ico", | |||||
| "robots.txt", | |||||
| "site.css" | |||||
| ] | |||||
| assert pages == sorted(expected) | |||||
| pages = [page.name for page in | |||||
| s.content.walk_resources_sorted(attr='name', reverse=True)] | |||||
| assert pages == sorted(expected, reverse=True) | |||||
| pages = [page.name for page in | |||||
| s.content.walk_resources_sorted(attr='source_file.kind')] | |||||
| assert pages == sorted(expected, key=lambda f: File(f).kind) | |||||
| from datetime import datetime, timedelta | |||||
| d = {} | |||||
| t = datetime.now() | |||||
| for name in expected: | |||||
| d[name] = t | |||||
| t = t - timedelta(days=1) | |||||
| for page in s.content.walk_resources(): | |||||
| page.meta = Expando(dict(time=d[page.name])) | |||||
| pages = [page.name for page in | |||||
| s.content.walk_resources_sorted(attr='meta.time', reverse=True)] | |||||
| assert pages == expected | |||||
| def test_contains_resource(): | def test_contains_resource(): | ||||
| s = Site(TEST_SITE_ROOT) | s = Site(TEST_SITE_ROOT) | ||||
| s.load() | s.load() | ||||