From 35637209e174ddd64827a563f1c9a1c7ef912ccb Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Sat, 22 Jan 2011 12:45:47 +0530 Subject: [PATCH] Added syntactic sugar for markings. Also refactored the template class. --- hyde/ext/plugins/auto_extend.py | 3 +- hyde/ext/plugins/blockdown.py | 58 +++++++++---------- hyde/ext/plugins/less.py | 1 + hyde/ext/plugins/markings.py | 47 ++++++++++++++++ hyde/ext/plugins/meta.py | 1 + hyde/ext/plugins/sorter.py | 1 + hyde/ext/plugins/texty.py | 98 +++++++++++++++++++++++++++++++++ hyde/ext/templates/jinja.py | 40 ++++++++++++++ hyde/generator.py | 1 + hyde/loader.py | 1 + hyde/model.py | 2 +- hyde/server.py | 1 + hyde/template.py | 73 ++++++++++-------------- hyde/tests/ext/test_markings.py | 69 +++++++++++++++++++++++ 14 files changed, 317 insertions(+), 79 deletions(-) create mode 100644 hyde/ext/plugins/markings.py create mode 100644 hyde/ext/plugins/texty.py create mode 100644 hyde/tests/ext/test_markings.py diff --git a/hyde/ext/plugins/auto_extend.py b/hyde/ext/plugins/auto_extend.py index ffdea40..71bedff 100644 --- a/hyde/ext/plugins/auto_extend.py +++ b/hyde/ext/plugins/auto_extend.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Autoextend css plugin """ @@ -29,7 +30,7 @@ class AutoExtendPlugin(Plugin): except AttributeError: pass if layout: - extends_pattern = self.template.extends_pattern + extends_pattern = self.template.patterns['extends'] if not re.search(extends_pattern, text): extended_text = self.template.get_extends_statement(layout) diff --git a/hyde/ext/plugins/blockdown.py b/hyde/ext/plugins/blockdown.py index 66b9c42..f94946b 100644 --- a/hyde/ext/plugins/blockdown.py +++ b/hyde/ext/plugins/blockdown.py @@ -1,34 +1,39 @@ +# -*- coding: utf-8 -*- """ -Blockdown css plugin +Blockdown plugin """ -from hyde.plugin import Plugin -from hyde.fs import File, Folder +from hyde.ext.plugins.texty import TextyPlugin -import re -from functools import partial - -class BlockdownPlugin(Plugin): +class BlockdownPlugin(TextyPlugin): """ - The plugin class for less css + The plugin class for block text replacement. """ - def __init__(self, site): super(BlockdownPlugin, self).__init__(site) - try: - self.open_pattern = site.config.blockdown.open_pattern - except AttributeError: - self.open_pattern = '^\s*===+\s*([A-Za-z0-9_\-.]+)\s*=*\s*$' - try: - self.close_pattern = site.config.blockdown.close_pattern - except AttributeError: - self.close_pattern = '^\s*===+\s*/+\s*=*/*([A-Za-z0-9_\-.]*)[\s=/]*$' + @property + def tag_name(self): + """ + The block tag. + """ + return 'block' - def template_loaded(self, template): - self.template = template + @property + def default_open_pattern(self): + """ + The default pattern for block open text. + """ + return '^\s*===+\s*([A-Za-z0-9_\-\.]+)\s*=*\s*$' + + @property + def default_close_pattern(self): + """ + The default pattern for block close text. + """ + return '^\s*===+\s*/+\s*=*/*([A-Za-z0-9_\-\.]*)[\s=/]*$' - def begin_text_resource(self, resource, text): + def text_to_tag(self, match, start=True): """ Replace open pattern (default:===[====]blockname[===========]) with @@ -37,15 +42,4 @@ class BlockdownPlugin(Plugin): with {% block blockname %} or equivalent """ - blocktag_open = re.compile(self.open_pattern, re.MULTILINE) - blocktag_close = re.compile(self.close_pattern, re.MULTILINE) - def blockdown_to_block(match, start_block=True): - if not match.lastindex: - return '' - block_name = match.groups(1)[0] - return (self.template.get_block_open_statement(block_name) - if start_block - else self.template.get_block_close_statement(block_name)) - text = blocktag_open.sub(blockdown_to_block, text) - text = blocktag_close.sub(partial(blockdown_to_block, start_block=False), text) - return text \ No newline at end of file + return super(BlockdownPlugin, self).text_to_tag(match, start) diff --git a/hyde/ext/plugins/less.py b/hyde/ext/plugins/less.py index d78fac6..a121f1b 100644 --- a/hyde/ext/plugins/less.py +++ b/hyde/ext/plugins/less.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Less css plugin """ diff --git a/hyde/ext/plugins/markings.py b/hyde/ext/plugins/markings.py new file mode 100644 index 0000000..98379bb --- /dev/null +++ b/hyde/ext/plugins/markings.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +Markings plugin +""" + +from hyde.ext.plugins.texty import TextyPlugin + +class MarkingsPlugin(TextyPlugin): + """ + The plugin class for mark text replacement. + """ + def __init__(self, site): + super(MarkingsPlugin, self).__init__(site) + + @property + def tag_name(self): + """ + The mark tag. + """ + return 'mark' + + @property + def default_open_pattern(self): + """ + The default pattern for mark open text. + """ + return u'^§§+\s*([A-Za-z0-9_\-]+)\s*$' + + @property + def default_close_pattern(self): + """ + The default pattern for mark close text. + """ + return u'^§§+\s*([A-Za-z0-9_\-]*)\s*\.\s*$' + + def text_to_tag(self, match, start=True): + """ + Replace open pattern (default:§§ CSS) + with + {% mark CSS %} or equivalent and + Replace close pattern (default: §§ CSS.) + with + {% endmark %} or equivalent + """ + text = super(MarkingsPlugin, self).text_to_tag(match, start) + print text + return text diff --git a/hyde/ext/plugins/meta.py b/hyde/ext/plugins/meta.py index 65c9233..b7839b7 100644 --- a/hyde/ext/plugins/meta.py +++ b/hyde/ext/plugins/meta.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Contains classes and utilities related to meta data in hyde. """ diff --git a/hyde/ext/plugins/sorter.py b/hyde/ext/plugins/sorter.py index 23957e0..0bd7449 100644 --- a/hyde/ext/plugins/sorter.py +++ b/hyde/ext/plugins/sorter.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Contains classes and utilities related to sortin resources and nodes in hyde. diff --git a/hyde/ext/plugins/texty.py b/hyde/ext/plugins/texty.py new file mode 100644 index 0000000..03614a4 --- /dev/null +++ b/hyde/ext/plugins/texty.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Provides classes and utilities that allow text +to be replaced before the templates are +rendered. +""" + +from hyde.plugin import Plugin + +import abc +import re +from functools import partial + + +class TextyPlugin(Plugin): + """ + Base class for text preprocessing plugins. + + Plugins that desire to provide syntactic sugar for + commonly used hyde functions for various templates + can inherit from this class. + """ + + __metaclass__ = abc.ABCMeta + + def __init__(self, site): + super(TextyPlugin, self).__init__(site) + + self.open_pattern = self.default_open_pattern + self.close_pattern = self.default_close_pattern + self.template = None + config = getattr(site.config, self.plugin_name, None) + + if config and hasattr(config, 'open_pattern'): + self.open_pattern = config.open_pattern + + if config and hasattr(config, 'close_pattern'): + self.open_pattern = config.close_pattern + + @property + def plugin_name(self): + """ + The name of the plugin. Makes an intelligent guess. + """ + return self.__class__.__name__.replace('Plugin', '').lower() + + @abc.abstractproperty + def tag_name(self): + """ + The tag that this plugin tries add syntactic sugar for. + """ + return self.plugin_name + + @abc.abstractproperty + def default_open_pattern(self): + """ + The default pattern for opening the tag. + """ + return None + + @abc.abstractproperty + def default_close_pattern(self): + """ + The default pattern for closing the tag. + """ + return None + + def template_loaded(self, template): + """ + Handles the template loaded event to keep + a reference to the template object. + """ + self.template = template + + @abc.abstractmethod + def text_to_tag(self, match, start=True): + """ + Replaces the matched text with tag statement + given by the template. + """ + if not match.lastindex: + return '' + params = match.groups(1)[0] + return (self.template.get_open_tag(self.tag_name, params) + if start + else self.template.get_close_tag(self.tag_name, params)) + + def begin_text_resource(self, resource, text): + """ + Replace a text base pattern with a template statement. + """ + text_open = re.compile(self.open_pattern, re.MULTILINE) + text_close = re.compile(self.close_pattern, re.MULTILINE) + + text = text_open.sub(self.text_to_tag, text) + text = text_close.sub( + partial(self.text_to_tag, start=False), text) + return text \ No newline at end of file diff --git a/hyde/ext/templates/jinja.py b/hyde/ext/templates/jinja.py index 0afd74e..7d84b7f 100644 --- a/hyde/ext/templates/jinja.py +++ b/hyde/ext/templates/jinja.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Jinja template utilties """ @@ -296,6 +297,45 @@ class Jinja2Template(Template): """ return TemplateError + @property + def patterns(self): + """ + The pattern for matching selected template statements. + """ + return { + "block_open": '\s*\{\%\s*block\s*([^\s]+)\s*\%\}', + "block_close": '\s*\{\%\s*endblock\s*([^\s]*)\s*\%\}', + "include": '\s*\{\%\s*include\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}', + "extends": '\s*\{\%\s*extends\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}' + } + + def get_include_statement(self, path_to_include): + """ + Returns an include statement for the current template, + given the path to include. + """ + return '{%% include \'%s\' %%}' % path_to_include + + def get_extends_statement(self, path_to_extend): + """ + Returns an extends statement for the current template, + given the path to extend. + """ + return '{%% extends \'%s\' %%}' % path_to_extend + + def get_open_tag(self, tag, params): + """ + Returns an open tag statement. + """ + return '{%% %s %s %%}' % (tag, params) + + def get_close_tag(self, tag, params): + """ + Returns an open tag statement. + """ + return '{%% end%s %%}' % tag + + def render(self, text, context): """ Renders the given resource using the context diff --git a/hyde/generator.py b/hyde/generator.py index 9c53465..2cb1f42 100644 --- a/hyde/generator.py +++ b/hyde/generator.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ The generator class and related utility functions. """ diff --git a/hyde/loader.py b/hyde/loader.py index 9b94618..fa8c5b8 100644 --- a/hyde/loader.py +++ b/hyde/loader.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Generic loader of extensions (plugins & templates) """ diff --git a/hyde/model.py b/hyde/model.py index 16387cf..43fa6d7 100644 --- a/hyde/model.py +++ b/hyde/model.py @@ -1,8 +1,8 @@ +# -*- coding: utf-8 -*- """ Contains data structures and utilities for hyde. """ - class Expando(object): """ A generic expando class that creates attributes from diff --git a/hyde/server.py b/hyde/server.py index ba06ba5..03120be 100644 --- a/hyde/server.py +++ b/hyde/server.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Contains classes and utilities for serving a site generated from hyde. diff --git a/hyde/template.py b/hyde/template.py index f384882..b11d5c8 100644 --- a/hyde/template.py +++ b/hyde/template.py @@ -6,6 +6,8 @@ Abstract classes and utilities for template engines from hyde.exceptions import HydeException from hyde.util import getLoggerWithNullHandler +import abc + class HtmlWrap(object): """ A wrapper class for raw html. @@ -38,10 +40,13 @@ class Template(object): the following interface must be implemented. """ + __metaclass__ = abc.ABCMeta + def __init__(self, sitepath): self.sitepath = sitepath self.logger = getLoggerWithNullHandler(self.__class__.__name__) + @abc.abstractmethod def configure(self, site, engine): """ The site object should contain a config attribute. The config object is @@ -64,7 +69,7 @@ class Template(object): context object that is populated with the appropriate variables for the given path. """ - abstract + return def get_dependencies(self, text): """ @@ -73,77 +78,55 @@ class Template(object): """ return None + @abc.abstractmethod def render(self, text, context): """ Given the text, and the context, this function must return the rendered string. """ - abstract + return '' - @property + @abc.abstractproperty def exception_class(self): return HydeException - @property - def include_pattern(self): + @abc.abstractproperty + def patterns(self): """ - The pattern for matching include statements + Patterns for matching selected template statements. """ + return {} - return '\s*\{\%\s*include\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}' - + @abc.abstractmethod def get_include_statement(self, path_to_include): """ Returns an include statement for the current template, given the path to include. """ - return "{%% include '%s' %%}" % path_to_include - - @property - def block_open_pattern(self): - """ - The pattern for matching include statements - """ - - return '\s*\{\%\s*block\s*([^\s]+)\s*\%\}' - - @property - def block_close_pattern(self): - """ - The pattern for matching include statements - """ - - return '\s*\{\%\s*endblock\s*([^\s]*)\s*\%\}' + return '{%% include \'%s\' %%}' % path_to_include - def get_block_open_statement(self, block_name): - """ - Returns a open block statement for the current template, - given the block name. - """ - return "{%% block %s %%}" % block_name - - def get_block_close_statement(self, block_name): + @abc.abstractmethod + def get_extends_statement(self, path_to_extend): """ - Returns a close block statement for the current template, - given the block name. + Returns an extends statement for the current template, + given the path to extend. """ - return "{%% endblock %s %%}" % block_name + return '{%% extends \'%s\' %%}' % path_to_extend - @property - def extends_pattern(self): + @abc.abstractmethod + def get_open_tag(self, tag, params): """ - The pattern for matching include statements + Returns an open tag statement. """ + return '{%% %s %s %%}' % (tag, params) - return '\s*\{\%\s*extends\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}' - - def get_extends_statement(self, path_to_extend): + @abc.abstractmethod + def get_close_tag(self, tag, params): """ - Returns an extends statement for the current template, - given the path to extend. + Returns an open tag statement. """ - return "{%% extends '%s' %%}" % path_to_extend + return '{%% end%s %%}' % tag @staticmethod def find_template(site): diff --git a/hyde/tests/ext/test_markings.py b/hyde/tests/ext/test_markings.py new file mode 100644 index 0000000..6675323 --- /dev/null +++ b/hyde/tests/ext/test_markings.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +Use nose +`$ pip install nose` +`$ nosetests` +""" +from hyde.fs import File, Folder +from hyde.generator import Generator +from hyde.site import Site + +from pyquery import PyQuery + +TEST_SITE = File(__file__).parent.parent.child_folder('_test') + + +class TestMarkings(object): + + def setUp(self): + TEST_SITE.make() + TEST_SITE.parent.child_folder( + 'sites/test_jinja').copy_contents_to(TEST_SITE) + + def tearDown(self): + TEST_SITE.delete() + + def test_markings(self): + text = u""" +=== +is_processable: False +=== +{% filter markdown|typogrify %} +§§ heading +This is a heading +================= +§§ heading. + +§§ content +Hyde & Jinja. +§§ . + +{% endfilter %} +""" + + text2 = """ +{% refer to "inc.md" as inc %} +{% filter markdown|typogrify %} +{{ inc.heading }} +{{ inc.content }} +{% endfilter %} +""" + site = Site(TEST_SITE) + site.config.plugins = [ + 'hyde.ext.plugins.meta.MetaPlugin', + 'hyde.ext.plugins.markings.MarkingsPlugin'] + inc = File(TEST_SITE.child('content/inc.md')) + inc.write(text) + site.load() + gen = Generator(site) + gen.load_template_if_needed() + template = gen.template + html = template.render(text2, {}).strip() + assert html + q = PyQuery(html) + assert "is_processable" not in html + assert "This is a" in q("h1").text() + assert "heading" in q("h1").text() + assert q(".amp").length == 1 + assert "mark" not in html + assert "reference" not in html \ No newline at end of file