@@ -4,11 +4,12 @@ Contains classes and utilities related to sortin | |||
resources and nodes in hyde. | |||
""" | |||
import re | |||
from hyde.model import Expando | |||
from hyde.plugin import Plugin | |||
from hyde.site import Node, Resource | |||
from functools import partial | |||
from itertools import ifilter, izip, tee | |||
from itertools import ifilter, izip, tee, product | |||
from operator import attrgetter | |||
def pairwalk(iterable): | |||
@@ -68,6 +69,29 @@ def add_method(obj, method_name, method_, 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): | |||
""" | |||
Sorter plugin for hyde. Adds the ability to do | |||
@@ -109,6 +133,7 @@ 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) | |||
@@ -127,3 +152,30 @@ class SorterPlugin(Plugin): | |||
for prev, next in pairwalk(walker()): | |||
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) |
@@ -31,41 +31,7 @@ class Generator(object): | |||
self.template = None | |||
Plugin.load_all(site) | |||
class PluginProxy(object): | |||
""" | |||
A proxy class to raise events in registered plugins | |||
""" | |||
def __init__(self, site): | |||
super(PluginProxy, self).__init__() | |||
self.site = site | |||
def __getattr__(self, method_name): | |||
if hasattr(Plugin, method_name): | |||
def __call_plugins__(*args): | |||
#logger.debug("Calling plugin method [%s]", method_name) | |||
res = None | |||
if self.site.plugins: | |||
for plugin in self.site.plugins: | |||
if hasattr(plugin, method_name): | |||
#logger.debug( | |||
# "\tCalling plugin [%s]", | |||
# plugin.__class__.__name__) | |||
function = getattr(plugin, method_name) | |||
res = function(*args) | |||
if res: | |||
targs = list(args) | |||
last = None | |||
if len(targs): | |||
last = targs.pop() | |||
targs.append(res if res else last) | |||
args = tuple(targs) | |||
return res | |||
return __call_plugins__ | |||
raise HydeException( | |||
"Unknown plugin method [%s] called." % method_name) | |||
self.events = PluginProxy(self.site) | |||
self.events = Plugin.get_proxy(self.site) | |||
@contextmanager | |||
def context_for_resource(self, resource): | |||
@@ -1,7 +1,7 @@ | |||
{% extends "root.j2" %} | |||
{% block main -%} | |||
<article> | |||
<hgroup class="article_head"> | |||
<hgroup> | |||
<h1 class="title">{{ resource.meta.title }}</h1> | |||
<h3 class="subtitle">{{ resource.meta.subtitle }}</h3> | |||
</hgroup> | |||
@@ -23,7 +23,7 @@ | |||
compatibility mode is within the first 1K bytes | |||
code.google.com/p/chromium/issues/detail?id=23003 --> | |||
<title>{% block title %}{{ resource.meta.title }} / Hyde Docs{% endblock %}</title> | |||
<title>{% block title %}{{ resource.meta.title }}{% endblock %}</title> | |||
<meta name="description" content="{{ resource.meta.description }}"> | |||
<meta name="author" content="{{ resource.meta.author }}"> | |||
@@ -8,6 +8,44 @@ from hyde import loader | |||
from hyde.util import getLoggerWithNullHandler | |||
from functools import partial | |||
logger = getLoggerWithNullHandler('hyde.engine') | |||
class PluginProxy(object): | |||
""" | |||
A proxy class to raise events in registered plugins | |||
""" | |||
def __init__(self, site): | |||
super(PluginProxy, self).__init__() | |||
self.site = site | |||
def __getattr__(self, method_name): | |||
if hasattr(Plugin, method_name): | |||
def __call_plugins__(*args): | |||
# logger.debug("Calling plugin method [%s]", method_name) | |||
res = None | |||
if self.site.plugins: | |||
for plugin in self.site.plugins: | |||
if hasattr(plugin, method_name): | |||
# logger.debug( | |||
# "\tCalling plugin [%s]", | |||
# plugin.__class__.__name__) | |||
function = getattr(plugin, method_name) | |||
res = function(*args) | |||
if res: | |||
targs = list(args) | |||
last = None | |||
if len(targs): | |||
last = targs.pop() | |||
targs.append(res if res else last) | |||
args = tuple(targs) | |||
return res | |||
return __call_plugins__ | |||
raise HydeException( | |||
"Unknown plugin method [%s] called." % method_name) | |||
class Plugin(object): | |||
""" | |||
The plugin protocol | |||
@@ -132,6 +170,9 @@ class Plugin(object): | |||
""" | |||
pass | |||
def raise_event(self, event_name): | |||
return getattr(Plugin.proxy, event_name)() | |||
@staticmethod | |||
def load_all(site): | |||
""" | |||
@@ -140,3 +181,7 @@ class Plugin(object): | |||
""" | |||
site.plugins = [loader.load_python_object(name)(site) | |||
for name in site.config.plugins] | |||
@staticmethod | |||
def get_proxy(site): | |||
return PluginProxy(site) |
@@ -29,7 +29,7 @@ class TestLess(object): | |||
def test_can_execute_less(self): | |||
s = Site(TEST_SITE) | |||
s.config.plugins = ['hyde.ext.plugins.less.LessCSSPlugin'] | |||
s.config.less = Expando(dict(app='/Users/lvfp/local/bin/lessc')) | |||
s.config.less = Expando(dict(app='~/local/bin/lessc')) | |||
source = TEST_SITE.child('content/media/css/site.less') | |||
target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) | |||
gen = Generator(s) | |||
@@ -4,6 +4,7 @@ Use nose | |||
`$ pip install nose` | |||
`$ nosetests` | |||
""" | |||
from hyde.ext.plugins.meta import MetaPlugin | |||
from hyde.ext.plugins.sorter import SorterPlugin | |||
from hyde.fs import File, Folder | |||
from hyde.generator import Generator | |||
@@ -136,55 +137,6 @@ class TestSorter(object): | |||
File(p).parent.name]))] | |||
assert pages == expected_sorted | |||
# def test_walk_resources_sorted_with_grouping_one_level(self): | |||
# s = Site(TEST_SITE) | |||
# cfg = """ | |||
# plugins: | |||
# - hyde.ext.sorter.SorterPlugin | |||
# sorter: | |||
# multi: | |||
# groups: sections.yaml | |||
# attr: | |||
# - source_file.kind | |||
# - node.name | |||
# | |||
# """ | |||
# sections = """ | |||
# group_name: section | |||
# groups: | |||
# - | |||
# name: support | |||
# description: All site support pages | |||
# - | |||
# name: media | |||
# description: Media files | |||
# """ | |||
# s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) | |||
# s.load() | |||
# SorterPlugin(s).begin_site() | |||
# | |||
# assert hasattr(s.content, 'walk_resources_sorted_by_multi') | |||
# expected = ["content/404.html", | |||
# "content/about.html", | |||
# "content/apple-touch-icon.png", | |||
# "content/blog/2010/december/merry-christmas.html", | |||
# "content/crossdomain.xml", | |||
# "content/favicon.ico", | |||
# "content/robots.txt", | |||
# "content/site.css" | |||
# ] | |||
# | |||
# pages = [page.name for page in s.content.walk_resources_sorted_by_multi()] | |||
# | |||
# expected_sorted = [File(page).name | |||
# for page in | |||
# sorted(expected, | |||
# key=lambda p: tuple( | |||
# [File(p).kind, | |||
# File(p).parent.name]))] | |||
# assert pages == expected_sorted | |||
def test_walk_resources_sorted_no_default_is_processable(self): | |||
s = Site(TEST_SITE) | |||
cfg = """ | |||
@@ -318,3 +270,67 @@ class TestSorter(object): | |||
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 |
@@ -1,8 +1 @@ | |||
{% extends "base.html" %} | |||
{% block main %} | |||
Hi! | |||
I am a test template to make sure jinja2 generation works well with hyde. | |||
{{resource.name}} | |||
{% endblock %} | |||
# Installation Guide |
@@ -0,0 +1,3 @@ | |||
extends: base.html | |||
default_block: main | |||
section: start |
@@ -1,8 +1 @@ | |||
{% extends "base.html" %} | |||
{% block main %} | |||
Hi! | |||
I am a test template to make sure jinja2 generation works well with hyde. | |||
{{resource.name}} | |||
{% endblock %} | |||
# Overview |
@@ -1,8 +1,4 @@ | |||
{% extends "base.html" %} | |||
{% block main %} | |||
Hi! | |||
I am a test template to make sure jinja2 generation works well with hyde. | |||
{{resource.name}} | |||
{% endblock %} | |||
=== | |||
section: plugins | |||
=== | |||
# Plugins |
@@ -1,8 +1,4 @@ | |||
{% extends "base.html" %} | |||
{% block main %} | |||
Hi! | |||
I am a test template to make sure jinja2 generation works well with hyde. | |||
{{resource.name}} | |||
{% endblock %} | |||
=== | |||
section: plugins | |||
=== | |||
# Tags |
@@ -1,8 +1 @@ | |||
{% extends "base.html" %} | |||
{% block main %} | |||
Hi! | |||
I am a test template to make sure jinja2 generation works well with hyde. | |||
{{resource.name}} | |||
{% endblock %} | |||
# Templating |
@@ -2,6 +2,5 @@ mode: development | |||
media_root:: media # Relative path from site root (the directory where this file exists) | |||
media_url: /media | |||
template: hyde.ext.jinja2 | |||
widgets: | |||
plugins: | |||
aggregators: | |||
meta: | |||
nodemeta: meta.yaml |