@@ -0,0 +1,132 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
Contains classes and utilities related to grouping | |||
resources and nodes in hyde. | |||
""" | |||
import re | |||
from hyde.model import Expando | |||
from hyde.plugin import Plugin | |||
from hyde.site import Node, Resource | |||
from hyde.util import add_method | |||
from functools import partial | |||
from itertools import ifilter, izip, tee, product | |||
from operator import attrgetter | |||
class Group(Expando): | |||
""" | |||
A wrapper class for groups. Adds methods for | |||
grouping resources. | |||
""" | |||
def __init__(self, grouping): | |||
super(Group, self).__init__(grouping) | |||
name = 'group' | |||
if hasattr(grouping, 'name'): | |||
name = grouping.name | |||
add_method(Node, | |||
'walk_resources_grouped_by_%s' % name, | |||
Group.walk_resources, | |||
group=self) | |||
def set_expando(self, key, value): | |||
""" | |||
If the key is groups, creates group objects instead of | |||
regular expando objects. | |||
""" | |||
if key == "groups": | |||
self.groups = [Group(group) for group in value] | |||
else: | |||
return super(Group, self).set_expando(key, value) | |||
@staticmethod | |||
def walk_resources(node, group): | |||
""" | |||
The method that gets attached to the node | |||
object. | |||
""" | |||
return group.list_resources(node) | |||
def walk_groups(self): | |||
""" | |||
Walks the groups in the current group | |||
""" | |||
for group in self.groups: | |||
yield group | |||
group.walk_groups() | |||
def list_resources(self, node): | |||
""" | |||
Lists the resources in the given node | |||
sorted based on sorter configuration in this | |||
group. | |||
""" | |||
walker = 'walk_resources' | |||
if hasattr(self, 'sort_with'): | |||
walker = 'walk_resources_sorted_by_' + self.sort_with | |||
walker = getattr(node, walker, getattr(node, 'walk_resources')) | |||
for resource in walker(): | |||
try: | |||
group_value = getattr(resource.meta, self.name) | |||
except AttributeError: | |||
continue | |||
if group_value == self.name: | |||
yield resource | |||
class GrouperPlugin(Plugin): | |||
""" | |||
Grouper plugin for hyde. Adds the ability to do | |||
group resources and nodes in an arbitrary | |||
hierarchy. | |||
Configuration example | |||
--------------------- | |||
#yaml | |||
sorter: | |||
kind: | |||
atts: source.kind | |||
grouper: | |||
hyde: | |||
# Categorizes the nodes and resources | |||
# based on the groups specified here. | |||
# The node and resource should be tagged | |||
# with the categories in their metadata | |||
sort_with: kind # A reference to the sorter | |||
description: Articles about hyde | |||
groups: | |||
- | |||
name: announcements | |||
description: Hyde release announcements | |||
- | |||
name: making of | |||
description: Articles about hyde design decisions | |||
- | |||
name: tips and tricks | |||
description: > | |||
Helpful snippets and tweaks to | |||
make hyde more awesome. | |||
""" | |||
def __init__(self, site): | |||
super(GrouperPlugin, self).__init__(site) | |||
def begin_site(self): | |||
""" | |||
Initialize plugin. Add the specified groups to the | |||
site context variable. | |||
""" | |||
config = self.site.config | |||
if not hasattr(config, 'grouper'): | |||
return | |||
if not hasattr(self.site, 'grouper'): | |||
self.site.grouper = {} | |||
for name, grouping in self.site.config.grouper.__dict__.items(): | |||
grouping.name = name | |||
self.site.grouper[name] = Group(grouping) |
@@ -1,12 +1,13 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
Contains classes and utilities related to sortin | |||
Contains classes and utilities related to sorting | |||
resources and nodes in hyde. | |||
""" | |||
import re | |||
from hyde.model import Expando | |||
from hyde.plugin import Plugin | |||
from hyde.site import Node, Resource | |||
from hyde.util import add_method | |||
from functools import partial | |||
from itertools import ifilter, izip, tee, product | |||
@@ -58,39 +59,6 @@ def sort_method(node, settings=None): | |||
key=attrgetter(*attr), | |||
reverse=reverse) | |||
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 SortGroup(Expando): | |||
# """ | |||
# A wrapper around sort groups. Understand hierarchical groups | |||
# and group metadata. | |||
# """ | |||
# | |||
# def update(self, d): | |||
# """ | |||
# Updates the Sortgroup with a new grouping | |||
# """ | |||
# | |||
# d = d or {} | |||
# if isinstance(d, dict): | |||
# for key, value in d.items(): | |||
# if key == "groups": | |||
# for group in value: | |||
# | |||
# setattr(self, key, Expando.transform(value)) | |||
# elif isinstance(d, Expando): | |||
# self.update(d.__dict__) | |||
class SorterPlugin(Plugin): | |||
""" | |||
@@ -133,10 +101,9 @@ class SorterPlugin(Plugin): | |||
return | |||
for name, settings in config.sorter.__dict__.items(): | |||
self._add_groups(name, settings) | |||
self.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) | |||
add_method(Node, sort_method_name, sort_method, settings=settings) | |||
match_method_name = 'is_%s' % name | |||
add_method(Resource, match_method_name, filter_method, settings) | |||
@@ -153,29 +120,3 @@ class SorterPlugin(Plugin): | |||
setattr(prev, next_att, next) | |||
setattr(next, prev_att, prev) | |||
def _add_groups(self, sorter_name, settings): | |||
""" | |||
Checks if the given `settings` contains a `groups` attribute. | |||
If it does, it loads the specified groups into the `site` object. | |||
""" | |||
if not hasattr(settings, 'grouping') or \ | |||
not hasattr(settings.grouping, 'groups') or \ | |||
not hasattr(settings.grouping, 'name'): | |||
return | |||
grouping = Expando(settings.grouping) | |||
setattr(self.site, sorter_name, grouping) | |||
group_dict = dict([(g.name, g) for g in grouping.groups]) | |||
for resource in self.site.content.walk_resources(): | |||
try: | |||
group_value = getattr(resource.meta, grouping.name) | |||
except AttributeError: | |||
continue | |||
if group_value in group_dict: | |||
group = group_dict[group_value] | |||
if not hasattr(group, 'resources'): | |||
group.resources = [] | |||
print "Adding resource[%s] to group[%s]" % (resource, group.name) | |||
group.resources.append(resource) |
@@ -26,12 +26,18 @@ class Expando(object): | |||
d = d or {} | |||
if isinstance(d, dict): | |||
for key, value in d.items(): | |||
setattr(self, key, Expando.transform(value)) | |||
self.set_expando(key, value) | |||
elif isinstance(d, Expando): | |||
self.update(d.__dict__) | |||
@staticmethod | |||
def transform(primitive): | |||
def set_expando(self, key, value): | |||
""" | |||
Sets the expando attribute after | |||
transforming the value. | |||
""" | |||
setattr(self, key, self.transform(value)) | |||
def transform(self, primitive): | |||
""" | |||
Creates an expando object, a sequence of expando objects or just | |||
returns the primitive based on the primitive's type. | |||
@@ -40,7 +46,7 @@ class Expando(object): | |||
return Expando(primitive) | |||
elif isinstance(primitive, (tuple, list, set, frozenset)): | |||
seq = type(primitive) | |||
return seq(Expando.transform(attr) for attr in primitive) | |||
return seq(self.transform(attr) for attr in primitive) | |||
else: | |||
return primitive | |||
@@ -0,0 +1,94 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
Use nose | |||
`$ pip install nose` | |||
`$ nosetests` | |||
""" | |||
from hyde.ext.plugins.meta import MetaPlugin | |||
from hyde.ext.plugins.sorter import SorterPlugin | |||
from hyde.ext.plugins.grouper import GrouperPlugin | |||
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 TestGrouper(object): | |||
def setUp(self): | |||
TEST_SITE.make() | |||
TEST_SITE.parent.child_folder( | |||
'sites/test_grouper').copy_contents_to(TEST_SITE) | |||
def tearDown(self): | |||
TEST_SITE.delete() | |||
def test_walk_resources_sorted_with_grouping_one_level(self): | |||
s = Site(TEST_SITE) | |||
cfg = """ | |||
plugins: | |||
- hyde.ext.meta.MetaPlugin | |||
- hyde.ext.sorter.SorterPlugin | |||
- hyde.ext.grouper.GrouperPlugin | |||
sorter: | |||
kind: | |||
attr: | |||
- source_file.kind | |||
grouper: | |||
sections: | |||
name: section | |||
description: Sections in the site | |||
sorter: kind | |||
groups: | |||
- | |||
name: start | |||
description: Getting Started | |||
- | |||
name: plugins | |||
description: Plugins | |||
""" | |||
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) | |||
s.load() | |||
MetaPlugin(s).begin_site() | |||
SorterPlugin(s).begin_site() | |||
GrouperPlugin(s).begin_site() | |||
groups = dict([(g.name, g) for g in s.grouper['sections'].groups]) | |||
assert 'start' in groups | |||
assert 'plugins' in groups | |||
assert hasattr(s.content, 'walk_resources_grouped_by_sections') | |||
# assert hasattr(s, 'sectional') | |||
# assert hasattr(s.sectional, 'groups') | |||
# assert len(s.sectional.groups) == 2 | |||
# | |||
# groups = dict([(g.name, g) for g in s.sectional.groups]) | |||
# | |||
# assert 'start' in groups | |||
# assert 'plugins' in groups | |||
# | |||
# start = groups['start'] | |||
# assert hasattr(start, 'resources') | |||
# start_resources = [resource.name for resource in | |||
# start.resources if resource.is_processable] | |||
# assert len(start_resources) == 3 | |||
# assert 'installation.html' in start_resources | |||
# assert 'overview.html' in start_resources | |||
# assert 'templating.html' in start_resources | |||
# | |||
# plugin = groups['plugins'] | |||
# assert hasattr(plugin, 'resources') | |||
# plugin_resources = [resource.name for resource in | |||
# plugin.resources if resource.is_processable] | |||
# assert len(plugin_resources) == 2 | |||
# assert 'plugins.html' in plugin_resources | |||
# assert 'tags.html' in plugin_resources | |||
# | |||
# |
@@ -269,68 +269,4 @@ class TestSorter(object): | |||
text = target.read_all() | |||
q = PyQuery(text) | |||
assert q('span.latest').text() == 'YayYayYay' | |||
class TestGrouper(object): | |||
def setUp(self): | |||
TEST_SITE.make() | |||
TEST_SITE.parent.child_folder( | |||
'sites/test_grouper').copy_contents_to(TEST_SITE) | |||
def tearDown(self): | |||
TEST_SITE.delete() | |||
def test_walk_resources_sorted_with_grouping_one_level(self): | |||
s = Site(TEST_SITE) | |||
cfg = """ | |||
plugins: | |||
- hyde.ext.meta.MetaPlugin | |||
- hyde.ext.sorter.SorterPlugin | |||
sorter: | |||
sectional: | |||
grouping: | |||
name: section | |||
description: Sections in the site | |||
groups: | |||
- | |||
name: start | |||
description: Getting Started | |||
- | |||
name: plugins | |||
description: Plugins | |||
attr: | |||
- source_file.kind | |||
- node.name | |||
""" | |||
s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) | |||
s.load() | |||
MetaPlugin(s).begin_site() | |||
SorterPlugin(s).begin_site() | |||
assert hasattr(s, 'sectional') | |||
assert hasattr(s.sectional, 'groups') | |||
assert len(s.sectional.groups) == 2 | |||
groups = dict([(g.name, g) for g in s.sectional.groups]) | |||
assert 'start' in groups | |||
assert 'plugins' in groups | |||
start = groups['start'] | |||
assert hasattr(start, 'resources') | |||
start_resources = [resource.name for resource in | |||
start.resources if resource.is_processable] | |||
assert len(start_resources) == 3 | |||
assert 'installation.html' in start_resources | |||
assert 'overview.html' in start_resources | |||
assert 'templating.html' in start_resources | |||
plugin = groups['plugins'] | |||
assert hasattr(plugin, 'resources') | |||
plugin_resources = [resource.name for resource in | |||
plugin.resources if resource.is_processable] | |||
assert len(plugin_resources) == 2 | |||
assert 'plugins.html' in plugin_resources | |||
assert 'tags.html' in plugin_resources | |||
assert q('span.latest').text() == 'YayYayYay' |
@@ -83,3 +83,15 @@ class ColorFormatter(logging.Formatter): | |||
return message + RESET_SEQ | |||
logging.ColorFormatter = ColorFormatter | |||
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_, *args, **kwargs): | |||
from functools import partial | |||
m = make_method(method_name, partial(method_, *args, **kwargs)) | |||
setattr(obj, method_name, m) |