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