Browse Source

Issue #112: Plugins now support inclusion filters.

-   `include_file_patterns` property accepts globs to filter by file name.
 -   `include_paths` accepts paths relative to content.
 -   `begin_node` and `node_complete` honor `include_paths`
 -   `begin_text_resource`, `text_resource_complete`, `begin_binary_resource`
      and `binary_resource_complete` honor both.
main
Lakshmi Vyasarajan 13 years ago
parent
commit
a69f1603b9
7 changed files with 121 additions and 18 deletions
  1. +10
    -0
      CHANGELOG.rst
  2. +1
    -1
      README.rst
  3. +1
    -1
      hyde/fs.py
  4. +65
    -12
      hyde/plugin.py
  5. +0
    -1
      hyde/tests/ext/test_tagger.py
  6. +43
    -2
      hyde/tests/test_plugin.py
  7. +1
    -1
      hyde/version.py

+ 10
- 0
CHANGELOG.rst View File

@@ -1,3 +1,13 @@
Version 0.8.5a6
============================================================

* Plugins now support inclusion filters. (Issue #112)
- `include_file_patterns` property accepts globs to filter by file name.
- `include_paths` accepts paths relative to content.
- `begin_node` and `node_complete` honor `include_paths`
- `begin_text_resource`, `text_resource_complete`, `begin_binary_resource`
and `binary_resource_complete` honor both.

Version 0.8.5a5
============================================================



+ 1
- 1
README.rst View File

@@ -1,4 +1,4 @@
Version 0.8.5a5
Version 0.8.5a6

A brand new **hyde**
====================


+ 1
- 1
hyde/fs.py View File

@@ -615,4 +615,4 @@ class Folder(FS):
"""
Return a `FolderLister` object
"""
return FolderLister(self)
return FolderLister(self)

+ 65
- 12
hyde/plugin.py View File

@@ -12,6 +12,7 @@ from hyde.util import getLoggerWithNullHandler, first_match, discover_executable
from hyde.model import Expando

from functools import partial
import fnmatch

import os
import re
@@ -40,14 +41,16 @@ class PluginProxy(object):
# logger.debug(
# "\tCalling plugin [%s]",
# plugin.__class__.__name__)
function = getattr(plugin, method_name)
res = function(*args)
targs = list(args)
if len(targs):
last = targs.pop()
res = res if res else last
targs.append(res)
args = tuple(targs)
checker = getattr(plugin, 'should_call__' + method_name)
if checker(*args):
function = getattr(plugin, method_name)
res = function(*args)
targs = list(args)
if len(targs):
last = targs.pop()
res = res if res else last
targs.append(res)
args = tuple(targs)
return res

return __call_plugins__
@@ -81,18 +84,30 @@ class Plugin(object):
"""
Syntactic sugar for template methods
"""
result = None
if name.startswith('t_') and self.template:
attr = name[2:]
if hasattr(self.template, attr):
return self.template[attr]
result = self.template[attr]
elif attr.endswith('_close_tag'):
tag = attr.replace('_close_tag', '')
return partial(self.template.get_close_tag, tag)
result = partial(self.template.get_close_tag, tag)
elif attr.endswith('_open_tag'):
tag = attr.replace('_open_tag', '')
return partial(self.template.get_open_tag, tag)
result = partial(self.template.get_open_tag, tag)
elif name.startswith('should_call__'):
(_, _, method) = name.rpartition('__')
if (method in ('begin_text_resource', 'text_resource_complete',
'begin_binary_resource', 'binary_resource_complete')):
result = self._file_filter
elif (method in ('begin_node', 'node_complete')):
result = self._dir_filter
else:
def always_true(*args, **kwargs):
return True
result = always_true

return super(Plugin, self).__getattribute__(name)
return result if result else super(Plugin, self).__getattribute__(name)

@property
def settings(self):
@@ -139,6 +154,44 @@ class Plugin(object):
"""
pass

def _file_filter(self, resource, *args, **kwargs):
"""
Returns True if the resource path matches the filter property in
plugin settings.
"""

if not self._dir_filter(resource.node, *args, **kwargs):
return False

try:
filters = self.settings.include_file_pattern
if not isinstance(filters, list):
filters = [filters]
except AttributeError:
filters = None
result = any(fnmatch.fnmatch(resource.path, f)
for f in filters) if filters else True
return result

def _dir_filter(self, node, *args, **kwargs):
"""
Returns True if the node path is a descendant of the include_paths property in
plugin settings.
"""
try:
node_filters = self.settings.include_paths
if not isinstance(node_filters, list):
node_filters = [node_filters]
node_filters = [self.site.content.node_from_relative_path(f)
for f in node_filters]
except AttributeError:
node_filters = None
result = any(node.source == f.source or
node.source.is_descendant_of(f.source)
for f in node_filters if f) \
if node_filters else True
return result

def begin_text_resource(self, resource, text):
"""
Called when a text resource is about to be processed for generation.


+ 0
- 1
hyde/tests/ext/test_tagger.py View File

@@ -217,7 +217,6 @@ Emotions:
from pyquery import PyQuery

q = PyQuery(archives['sad'].read_all())
print q
assert len(q("li.emotion")) == 2
assert q("#author").text() == "Tagger Plugin"



+ 43
- 2
hyde/tests/test_plugin.py View File

@@ -10,8 +10,9 @@ from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.plugin import Plugin
from hyde.site import Site
from hyde.model import Expando

from mock import patch
from mock import patch, Mock
from nose.tools import raises, nottest, with_setup


@@ -328,4 +329,44 @@ class TestPlugins(object):
gen.generate_resource_at_path(path)
about = File(Folder(
self.site.config.deploy_root_path).child('about.html'))
assert about.read_all() == "Jam"
assert about.read_all() == "Jam"

def test_plugin_filters_begin_text_resource(self):
def empty_return(self, resource, text=''):
return text
with patch.object(ConstantReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock2:
self.site.config.plugins = [
'hyde.tests.test_plugin.ConstantReturnPlugin',
'hyde.tests.test_plugin.NoReturnPlugin'
]
self.site.config.constantreturn = Expando(dict(include_file_pattern="*.css"))
self.site.config.noreturn = Expando(dict(include_file_pattern=["*.html", "*.txt"]))
gen = Generator(self.site)
gen.generate_all()
mock1_args = sorted(set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(set([arg[0][0].name for arg in mock2.call_args_list]))
assert len(mock1_args) == 1
assert len(mock2_args) == 4
assert mock1_args == ["site.css"]
assert mock2_args == ["404.html", "about.html", "merry-christmas.html", "robots.txt"]

def test_plugin_node_filters_begin_text_resource(self):
def empty_return(*args, **kwargs):
return None
with patch.object(ConstantReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock1:
with patch.object(NoReturnPlugin, 'begin_text_resource', new=Mock(wraps=empty_return)) as mock2:
self.site.config.plugins = [
'hyde.tests.test_plugin.ConstantReturnPlugin',
'hyde.tests.test_plugin.NoReturnPlugin'
]
self.site.config.constantreturn = Expando(dict(include_paths="media"))
self.site.config.noreturn = Expando(dict(include_file_pattern="*.html", include_paths=["blog"]))
gen = Generator(self.site)
gen.generate_all()
mock1_args = sorted(set([arg[0][0].name for arg in mock1.call_args_list]))
mock2_args = sorted(set([arg[0][0].name for arg in mock2.call_args_list]))
assert len(mock1_args) == 1
assert len(mock2_args) == 1
assert mock1_args == ["site.css"]
assert mock2_args == ["merry-christmas.html"]

+ 1
- 1
hyde/version.py View File

@@ -3,4 +3,4 @@
Handles hyde version
TODO: Use fabric like versioning scheme
"""
__version__ = '0.8.5a5'
__version__ = '0.8.5a6'

Loading…
Cancel
Save