@@ -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 -*- | # -*- coding: utf-8 -*- | ||||
""" | """ | ||||
Contains classes and utilities related to sortin | |||||
Contains classes and utilities related to sorting | |||||
resources and nodes in hyde. | resources and nodes in hyde. | ||||
""" | """ | ||||
import re | import re | ||||
from hyde.model import Expando | from hyde.model import Expando | ||||
from hyde.plugin import Plugin | from hyde.plugin import Plugin | ||||
from hyde.site import Node, Resource | from hyde.site import Node, Resource | ||||
from hyde.util import add_method | |||||
from functools import partial | from functools import partial | ||||
from itertools import ifilter, izip, tee, product | from itertools import ifilter, izip, tee, product | ||||
@@ -58,39 +59,6 @@ def sort_method(node, settings=None): | |||||
key=attrgetter(*attr), | key=attrgetter(*attr), | ||||
reverse=reverse) | 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): | class SorterPlugin(Plugin): | ||||
""" | """ | ||||
@@ -133,10 +101,9 @@ class SorterPlugin(Plugin): | |||||
return | return | ||||
for name, settings in config.sorter.__dict__.items(): | for name, settings in config.sorter.__dict__.items(): | ||||
self._add_groups(name, settings) | |||||
self.logger.info("Adding sort methods for [%s]" % name) | self.logger.info("Adding sort methods for [%s]" % name) | ||||
sort_method_name = 'walk_resources_sorted_by_%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 | match_method_name = 'is_%s' % name | ||||
add_method(Resource, match_method_name, filter_method, settings) | add_method(Resource, match_method_name, filter_method, settings) | ||||
@@ -153,29 +120,3 @@ class SorterPlugin(Plugin): | |||||
setattr(prev, next_att, next) | setattr(prev, next_att, next) | ||||
setattr(next, prev_att, prev) | 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 {} | d = d or {} | ||||
if isinstance(d, dict): | if isinstance(d, dict): | ||||
for key, value in d.items(): | for key, value in d.items(): | ||||
setattr(self, key, Expando.transform(value)) | |||||
self.set_expando(key, value) | |||||
elif isinstance(d, Expando): | elif isinstance(d, Expando): | ||||
self.update(d.__dict__) | 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 | Creates an expando object, a sequence of expando objects or just | ||||
returns the primitive based on the primitive's type. | returns the primitive based on the primitive's type. | ||||
@@ -40,7 +46,7 @@ class Expando(object): | |||||
return Expando(primitive) | return Expando(primitive) | ||||
elif isinstance(primitive, (tuple, list, set, frozenset)): | elif isinstance(primitive, (tuple, list, set, frozenset)): | ||||
seq = type(primitive) | seq = type(primitive) | ||||
return seq(Expando.transform(attr) for attr in primitive) | |||||
return seq(self.transform(attr) for attr in primitive) | |||||
else: | else: | ||||
return primitive | 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() | text = target.read_all() | ||||
q = PyQuery(text) | 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 | return message + RESET_SEQ | ||||
logging.ColorFormatter = ColorFormatter | 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) |