| @@ -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: | |||
| 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 | |||
| 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() | |||
| 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(): | |||
| s = Site(TEST_SITE_ROOT) | |||
| s.load() | |||