- `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
| @@ -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,4 +1,4 @@ | |||
| Version 0.8.5a5 | |||
| Version 0.8.5a6 | |||
| A brand new **hyde** | |||
| ==================== | |||
| @@ -615,4 +615,4 @@ class Folder(FS): | |||
| """ | |||
| Return a `FolderLister` object | |||
| """ | |||
| return FolderLister(self) | |||
| return FolderLister(self) | |||
| @@ -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. | |||
| @@ -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" | |||
| @@ -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"] | |||
| @@ -3,4 +3,4 @@ | |||
| Handles hyde version | |||
| TODO: Use fabric like versioning scheme | |||
| """ | |||
| __version__ = '0.8.5a5' | |||
| __version__ = '0.8.5a6' | |||