@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Autoextend css plugin | Autoextend css plugin | ||||
""" | """ | ||||
@@ -29,7 +30,7 @@ class AutoExtendPlugin(Plugin): | |||||
except AttributeError: | except AttributeError: | ||||
pass | pass | ||||
if layout: | if layout: | ||||
extends_pattern = self.template.extends_pattern | |||||
extends_pattern = self.template.patterns['extends'] | |||||
if not re.search(extends_pattern, text): | if not re.search(extends_pattern, text): | ||||
extended_text = self.template.get_extends_statement(layout) | extended_text = self.template.get_extends_statement(layout) | ||||
@@ -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): | def __init__(self, site): | ||||
super(BlockdownPlugin, self).__init__(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[===========]) | Replace open pattern (default:===[====]blockname[===========]) | ||||
with | with | ||||
@@ -37,15 +42,4 @@ class BlockdownPlugin(Plugin): | |||||
with | with | ||||
{% block blockname %} or equivalent | {% 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 | |||||
return super(BlockdownPlugin, self).text_to_tag(match, start) |
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Less css plugin | Less css plugin | ||||
""" | """ | ||||
@@ -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 |
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Contains classes and utilities related to meta data in hyde. | Contains classes and utilities related to meta data in hyde. | ||||
""" | """ | ||||
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Contains classes and utilities related to sortin | Contains classes and utilities related to sortin | ||||
resources and nodes in hyde. | resources and nodes in hyde. | ||||
@@ -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 |
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Jinja template utilties | Jinja template utilties | ||||
""" | """ | ||||
@@ -296,6 +297,45 @@ class Jinja2Template(Template): | |||||
""" | """ | ||||
return TemplateError | 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): | def render(self, text, context): | ||||
""" | """ | ||||
Renders the given resource using the context | Renders the given resource using the context | ||||
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
The generator class and related utility functions. | The generator class and related utility functions. | ||||
""" | """ | ||||
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Generic loader of extensions (plugins & templates) | Generic loader of extensions (plugins & templates) | ||||
""" | """ | ||||
@@ -1,8 +1,8 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Contains data structures and utilities for hyde. | Contains data structures and utilities for hyde. | ||||
""" | """ | ||||
class Expando(object): | class Expando(object): | ||||
""" | """ | ||||
A generic expando class that creates attributes from | A generic expando class that creates attributes from | ||||
@@ -1,3 +1,4 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | """ | ||||
Contains classes and utilities for serving a site | Contains classes and utilities for serving a site | ||||
generated from hyde. | generated from hyde. | ||||
@@ -6,6 +6,8 @@ Abstract classes and utilities for template engines | |||||
from hyde.exceptions import HydeException | from hyde.exceptions import HydeException | ||||
from hyde.util import getLoggerWithNullHandler | from hyde.util import getLoggerWithNullHandler | ||||
import abc | |||||
class HtmlWrap(object): | class HtmlWrap(object): | ||||
""" | """ | ||||
A wrapper class for raw html. | A wrapper class for raw html. | ||||
@@ -38,10 +40,13 @@ class Template(object): | |||||
the following interface must be implemented. | the following interface must be implemented. | ||||
""" | """ | ||||
__metaclass__ = abc.ABCMeta | |||||
def __init__(self, sitepath): | def __init__(self, sitepath): | ||||
self.sitepath = sitepath | self.sitepath = sitepath | ||||
self.logger = getLoggerWithNullHandler(self.__class__.__name__) | self.logger = getLoggerWithNullHandler(self.__class__.__name__) | ||||
@abc.abstractmethod | |||||
def configure(self, site, engine): | def configure(self, site, engine): | ||||
""" | """ | ||||
The site object should contain a config attribute. The config object is | 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 | context object that is populated with the appropriate variables for the given | ||||
path. | path. | ||||
""" | """ | ||||
abstract | |||||
return | |||||
def get_dependencies(self, text): | def get_dependencies(self, text): | ||||
""" | """ | ||||
@@ -73,77 +78,55 @@ class Template(object): | |||||
""" | """ | ||||
return None | return None | ||||
@abc.abstractmethod | |||||
def render(self, text, context): | def render(self, text, context): | ||||
""" | """ | ||||
Given the text, and the context, this function must return the | Given the text, and the context, this function must return the | ||||
rendered string. | rendered string. | ||||
""" | """ | ||||
abstract | |||||
return '' | |||||
@property | |||||
@abc.abstractproperty | |||||
def exception_class(self): | def exception_class(self): | ||||
return HydeException | 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): | def get_include_statement(self, path_to_include): | ||||
""" | """ | ||||
Returns an include statement for the current template, | Returns an include statement for the current template, | ||||
given the path to include. | 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 | @staticmethod | ||||
def find_template(site): | def find_template(site): | ||||
@@ -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 |