diff --git a/hyde/ext/plugins/grouper.py b/hyde/ext/plugins/grouper.py new file mode 100644 index 0000000..6db8148 --- /dev/null +++ b/hyde/ext/plugins/grouper.py @@ -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) \ No newline at end of file diff --git a/hyde/ext/plugins/linker.py b/hyde/ext/plugins/linker.py new file mode 100644 index 0000000..e69de29 diff --git a/hyde/ext/plugins/sorter.py b/hyde/ext/plugins/sorter.py index ed6b58e..da08271 100644 --- a/hyde/ext/plugins/sorter.py +++ b/hyde/ext/plugins/sorter.py @@ -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) \ No newline at end of file diff --git a/hyde/model.py b/hyde/model.py index a53f844..8ad92c5 100644 --- a/hyde/model.py +++ b/hyde/model.py @@ -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 diff --git a/hyde/tests/ext/test_grouper.py b/hyde/tests/ext/test_grouper.py new file mode 100644 index 0000000..09e80c0 --- /dev/null +++ b/hyde/tests/ext/test_grouper.py @@ -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 + # + # \ No newline at end of file diff --git a/hyde/tests/ext/test_sorter.py b/hyde/tests/ext/test_sorter.py index ac7ab74..21770dd 100644 --- a/hyde/tests/ext/test_sorter.py +++ b/hyde/tests/ext/test_sorter.py @@ -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 \ No newline at end of file + assert q('span.latest').text() == 'YayYayYay' \ No newline at end of file diff --git a/hyde/util.py b/hyde/util.py index 8ea7f96..5c7268e 100644 --- a/hyde/util.py +++ b/hyde/util.py @@ -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) \ No newline at end of file