@@ -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() | ||||