From a69f1603b927f11c7466b33bc7ebfcdfc914b8f6 Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Wed, 23 Nov 2011 23:28:33 +0530 Subject: [PATCH] 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. --- CHANGELOG.rst | 10 +++++ README.rst | 2 +- hyde/fs.py | 2 +- hyde/plugin.py | 77 +++++++++++++++++++++++++++++------ hyde/tests/ext/test_tagger.py | 1 - hyde/tests/test_plugin.py | 45 +++++++++++++++++++- hyde/version.py | 2 +- 7 files changed, 121 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba4f920..a5b5a3c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ============================================================ diff --git a/README.rst b/README.rst index 0f73c60..a7c37c3 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Version 0.8.5a5 +Version 0.8.5a6 A brand new **hyde** ==================== diff --git a/hyde/fs.py b/hyde/fs.py index 73fc1fc..cf4bb8e 100644 --- a/hyde/fs.py +++ b/hyde/fs.py @@ -615,4 +615,4 @@ class Folder(FS): """ Return a `FolderLister` object """ - return FolderLister(self) + return FolderLister(self) \ No newline at end of file diff --git a/hyde/plugin.py b/hyde/plugin.py index 33f8a19..c11e18f 100644 --- a/hyde/plugin.py +++ b/hyde/plugin.py @@ -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. diff --git a/hyde/tests/ext/test_tagger.py b/hyde/tests/ext/test_tagger.py index 08333f8..641c459 100644 --- a/hyde/tests/ext/test_tagger.py +++ b/hyde/tests/ext/test_tagger.py @@ -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" diff --git a/hyde/tests/test_plugin.py b/hyde/tests/test_plugin.py index 042d0d7..6df301e 100644 --- a/hyde/tests/test_plugin.py +++ b/hyde/tests/test_plugin.py @@ -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" \ No newline at end of file + 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"] \ No newline at end of file diff --git a/hyde/version.py b/hyde/version.py index b8eb697..5efa6b2 100644 --- a/hyde/version.py +++ b/hyde/version.py @@ -3,4 +3,4 @@ Handles hyde version TODO: Use fabric like versioning scheme """ -__version__ = '0.8.5a5' +__version__ = '0.8.5a6'