From b7ce44f305459234c7fe2c1f90dc9e980e49ae36 Mon Sep 17 00:00:00 2001
From: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Date: Sat, 31 Mar 2012 21:19:04 -0400
Subject: [PATCH] Move the grouper plugin into meta module.

---
 hyde/ext/plugins/meta.py       | 207 ++++++++++++++++++++++++++++++++-
 hyde/layouts/starter/site.yaml |   2 +-
 hyde/tests/ext/test_grouper.py |   9 +-
 3 files changed, 213 insertions(+), 5 deletions(-)

diff --git a/hyde/ext/plugins/meta.py b/hyde/ext/plugins/meta.py
index 7b1defe..c6635d6 100644
--- a/hyde/ext/plugins/meta.py
+++ b/hyde/ext/plugins/meta.py
@@ -4,14 +4,15 @@ Contains classes and utilities related to meta data in hyde.
 """
 
 import re
+from collections import namedtuple
 from operator import attrgetter
 from itertools import ifilter
 from functools import partial
 from hyde.model import Expando
 from hyde.plugin import Plugin
 from hyde.fs import File, Folder
-from hyde.site import Node
-from hyde.util import add_method
+from hyde.site import Node, Resource
+from hyde.util import add_method, add_property, pairwalk
 import yaml
 
 
@@ -513,3 +514,205 @@ class SorterPlugin(Plugin):
                 setattr(prev, next_att, next)
                 setattr(next, prev_att, prev)
 
+
+Grouper = namedtuple('Grouper', 'group resources')
+
+class Group(Expando):
+    """
+    A wrapper class for groups. Adds methods for
+    grouping resources.
+    """
+
+    def __init__(self, grouping, parent=None):
+        self.name = 'groups'
+        self.parent = parent
+        self.root = self
+        self.root = parent.root if parent else self
+        self.groups = []
+        self.sorter = getattr(grouping, 'sorter', None)
+        if hasattr(parent, 'sorter'):
+            self.sorter = parent.sorter
+        super(Group, self).__init__(grouping)
+
+        add_method(Node,
+                'walk_%s_groups' % self.name,
+                Group.walk_groups_in_node,
+                group=self)
+        add_method(Node,
+                'walk_resources_grouped_by_%s' % self.name,
+                Group.walk_resources,
+                group=self)
+        add_property(Resource,
+                    '%s_group' % self.name,
+                    Group.get_resource_group,
+                    group=self)
+        add_method(Resource,
+                    'walk_%s_groups' % self.name,
+                    Group.walk_resource_groups,
+                    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, parent=self) for group in value]
+        else:
+            return super(Group, self).set_expando(key, value)
+
+    @staticmethod
+    def get_resource_group(resource, group):
+        """
+        This method gets attached to the resource object.
+        Returns group and its ancestors that the resource
+        belongs to, in that order.
+        """
+        try:
+            group_name = getattr(resource.meta, group.root.name)
+        except AttributeError:
+            group_name = None
+
+        return next((g for g in group.walk_groups()
+                            if g.name == group_name), None) \
+                    if group_name \
+                    else None
+
+    @staticmethod
+    def walk_resource_groups(resource, group):
+        """
+        This method gets attached to the resource object.
+        Returns group and its ancestors that the resource
+        belongs to, in that order.
+        """
+        try:
+            group_name = getattr(resource.meta, group.root.name)
+        except AttributeError:
+            group_name = None
+        if group_name:
+            for g in group.walk_groups():
+                if g.name == group_name:
+                    return reversed(list(g.walk_hierarchy()))
+        return []
+
+    @staticmethod
+    def walk_resources(node, group):
+        """
+        The method that gets attached to the node
+        object for walking the resources in the node
+        that belong to this group.
+        """
+        for group in group.walk_groups():
+            for resource in group.walk_resources_in_node(node):
+                yield resource
+
+    @staticmethod
+    def walk_groups_in_node(node, group):
+        """
+        The method that gets attached to the node
+        object for walking the groups in the node.
+        """
+        walker = group.walk_groups()
+        for g in walker:
+            lister = g.walk_resources_in_node(node)
+            yield Grouper(group=g, resources=lister)
+
+    def walk_hierarchy(self):
+        """
+        Walks the group hierarchy starting from
+        this group.
+        """
+        g = self
+        yield g
+        while g.parent:
+            yield g.parent
+            g = g.parent
+
+    def walk_groups(self):
+        """
+        Walks the groups in the current group
+        """
+        yield self
+        for group in self.groups:
+            for child in group.walk_groups():
+                yield child
+
+    def walk_resources_in_node(self, node):
+        """
+        Walks the resources in the given node
+        sorted based on sorter configuration in this
+        group.
+        """
+        walker = 'walk_resources'
+        if hasattr(self, 'sorter') and self.sorter:
+            walker = 'walk_resources_sorted_by_' + self.sorter
+        walker = getattr(node, walker, getattr(node, 'walk_resources'))
+        for resource in walker():
+            try:
+                group_value = getattr(resource.meta, self.root.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
+           sorter: 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
+            prev_att = 'prev_in_%s' % name
+            next_att = 'next_in_%s' % name
+            setattr(Resource, prev_att, None)
+            setattr(Resource, next_att, None)
+            self.site.grouper[name] = Group(grouping)
+            walker = Group.walk_resources(
+                            self.site.content, self.site.grouper[name])
+
+            for prev, next in pairwalk(walker):
+                setattr(next, prev_att, prev)
+                setattr(prev, next_att, next)
+
diff --git a/hyde/layouts/starter/site.yaml b/hyde/layouts/starter/site.yaml
index 901719d..6ec17d8 100644
--- a/hyde/layouts/starter/site.yaml
+++ b/hyde/layouts/starter/site.yaml
@@ -13,7 +13,7 @@ plugins:
     - hyde.ext.plugins.meta.AutoExtendPlugin
     # Plugins needed for the advances section.
     - hyde.ext.plugins.meta.SorterPlugin
-    - hyde.ext.plugins.grouper.GrouperPlugin
+    - hyde.ext.plugins.meta.GrouperPlugin
     - hyde.ext.plugins.meta.TaggerPlugin
 context:
     data:
diff --git a/hyde/tests/ext/test_grouper.py b/hyde/tests/ext/test_grouper.py
index 0a17e47..80d3735 100644
--- a/hyde/tests/ext/test_grouper.py
+++ b/hyde/tests/ext/test_grouper.py
@@ -6,7 +6,12 @@ Use nose
 """
 from hyde.ext.plugins.meta import MetaPlugin
 from hyde.ext.plugins.meta import SorterPlugin
+<<<<<<< HEAD
 from hyde.ext.plugins.grouper import GrouperPlugin
+=======
+from hyde.ext.plugins.meta import GrouperPlugin
+from hyde.fs import File, Folder
+>>>>>>> Move the grouper plugin into meta module.
 from hyde.generator import Generator
 from hyde.site import Site
 from hyde.model import Config, Expando
@@ -31,7 +36,7 @@ class TestGrouperSingleLevel(object):
         plugins:
           - hyde.ext.plugins.meta.MetaPlugin
           - hyde.ext.plugins.meta.SorterPlugin
-          - hyde.ext.plugins.grouper.GrouperPlugin
+          - hyde.ext.plugins.meta.GrouperPlugin
         sorter:
           kind:
               attr:
@@ -237,7 +242,7 @@ class TestGrouperSingleLevel(object):
         plugins:
           - hyde.ext.plugins.meta.MetaPlugin
           - hyde.ext.plugins.meta.SorterPlugin
-          - hyde.ext.plugins.grouper.GrouperPlugin
+          - hyde.ext.plugins.meta.GrouperPlugin
         sorter:
           kind:
               attr: