|
|
@@ -3,7 +3,7 @@ Jinja template utilties |
|
|
""" |
|
|
""" |
|
|
|
|
|
|
|
|
from hyde.fs import File, Folder |
|
|
from hyde.fs import File, Folder |
|
|
from hyde.template import Template |
|
|
|
|
|
|
|
|
from hyde.template import HtmlWrap, Template |
|
|
from jinja2 import contextfunction, Environment, FileSystemLoader |
|
|
from jinja2 import contextfunction, Environment, FileSystemLoader |
|
|
from jinja2 import environmentfilter, Markup, Undefined, nodes |
|
|
from jinja2 import environmentfilter, Markup, Undefined, nodes |
|
|
from jinja2.ext import Extension |
|
|
from jinja2.ext import Extension |
|
|
@@ -11,6 +11,9 @@ from jinja2.exceptions import TemplateError |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SilentUndefined(Undefined): |
|
|
class SilentUndefined(Undefined): |
|
|
|
|
|
""" |
|
|
|
|
|
A redefinition of undefined that eats errors. |
|
|
|
|
|
""" |
|
|
def __getattr__(self, name): |
|
|
def __getattr__(self, name): |
|
|
return self |
|
|
return self |
|
|
|
|
|
|
|
|
@@ -19,20 +22,28 @@ class SilentUndefined(Undefined): |
|
|
def __call__(self, *args, **kwargs): |
|
|
def __call__(self, *args, **kwargs): |
|
|
return self |
|
|
return self |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextfunction |
|
|
@contextfunction |
|
|
def media_url(context, path): |
|
|
def media_url(context, path): |
|
|
|
|
|
""" |
|
|
|
|
|
Returns the media url given a partial path. |
|
|
|
|
|
""" |
|
|
site = context['site'] |
|
|
site = context['site'] |
|
|
return Folder(site.config.media_url).child(path) |
|
|
return Folder(site.config.media_url).child(path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextfunction |
|
|
@contextfunction |
|
|
def content_url(context, path): |
|
|
def content_url(context, path): |
|
|
|
|
|
""" |
|
|
|
|
|
Returns the content url given a partial path. |
|
|
|
|
|
""" |
|
|
site = context['site'] |
|
|
site = context['site'] |
|
|
return Folder(site.config.base_url).child(path) |
|
|
return Folder(site.config.base_url).child(path) |
|
|
|
|
|
|
|
|
@environmentfilter |
|
|
@environmentfilter |
|
|
def markdown(env, value): |
|
|
def markdown(env, value): |
|
|
|
|
|
""" |
|
|
|
|
|
Markdown filter with support for extensions. |
|
|
|
|
|
""" |
|
|
try: |
|
|
try: |
|
|
import markdown |
|
|
import markdown |
|
|
except ImportError: |
|
|
except ImportError: |
|
|
@@ -46,35 +57,53 @@ def markdown(env, value): |
|
|
return md.convert(output) |
|
|
return md.convert(output) |
|
|
|
|
|
|
|
|
class Markdown(Extension): |
|
|
class Markdown(Extension): |
|
|
|
|
|
""" |
|
|
|
|
|
A wrapper around the markdown filter for syntactic sugar. |
|
|
|
|
|
""" |
|
|
tags = set(['markdown']) |
|
|
tags = set(['markdown']) |
|
|
|
|
|
|
|
|
def parse(self, parser): |
|
|
def parse(self, parser): |
|
|
|
|
|
""" |
|
|
|
|
|
Parses the statements and defers to the callback for markdown processing. |
|
|
|
|
|
""" |
|
|
lineno = parser.stream.next().lineno |
|
|
lineno = parser.stream.next().lineno |
|
|
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) |
|
|
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) |
|
|
|
|
|
|
|
|
return nodes.CallBlock( |
|
|
return nodes.CallBlock( |
|
|
self.call_method('_render_markdown', [], [], None, None), |
|
|
|
|
|
[], [], body |
|
|
|
|
|
).set_lineno(lineno) |
|
|
|
|
|
|
|
|
self.call_method('_render_markdown'), |
|
|
|
|
|
[], [], body).set_lineno(lineno) |
|
|
|
|
|
|
|
|
def _render_markdown(self, caller=None): |
|
|
def _render_markdown(self, caller=None): |
|
|
|
|
|
""" |
|
|
|
|
|
Calls the markdown filter to transform the output. |
|
|
|
|
|
""" |
|
|
if not caller: |
|
|
if not caller: |
|
|
return '' |
|
|
return '' |
|
|
output = caller().strip() |
|
|
output = caller().strip() |
|
|
return markdown(self.environment, output) |
|
|
return markdown(self.environment, output) |
|
|
|
|
|
|
|
|
class IncludeText(Extension): |
|
|
class IncludeText(Extension): |
|
|
|
|
|
""" |
|
|
|
|
|
Automatically runs `markdown` and `typogrify` on included |
|
|
|
|
|
files. |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
tags = set(['includetext']) |
|
|
tags = set(['includetext']) |
|
|
|
|
|
|
|
|
def parse(self, parser): |
|
|
def parse(self, parser): |
|
|
|
|
|
""" |
|
|
|
|
|
Delegates all the parsing to the native include node. |
|
|
|
|
|
""" |
|
|
node = parser.parse_include() |
|
|
node = parser.parse_include() |
|
|
return nodes.CallBlock( |
|
|
return nodes.CallBlock( |
|
|
self.call_method('_render_include_text', [], [], None, None), |
|
|
|
|
|
[], [], [node] |
|
|
|
|
|
).set_lineno(node.lineno) |
|
|
|
|
|
|
|
|
self.call_method('_render_include_text'), |
|
|
|
|
|
[], [], [node]).set_lineno(node.lineno) |
|
|
|
|
|
|
|
|
def _render_include_text(self, caller=None): |
|
|
def _render_include_text(self, caller=None): |
|
|
|
|
|
""" |
|
|
|
|
|
Runs markdown and if available, typogrigy on the |
|
|
|
|
|
content returned by the include node. |
|
|
|
|
|
""" |
|
|
if not caller: |
|
|
if not caller: |
|
|
return '' |
|
|
return '' |
|
|
output = caller().strip() |
|
|
output = caller().strip() |
|
|
@@ -84,8 +113,89 @@ class IncludeText(Extension): |
|
|
output = typo(output) |
|
|
output = typo(output) |
|
|
return output |
|
|
return output |
|
|
|
|
|
|
|
|
|
|
|
MARKINGS = '_markings_' |
|
|
|
|
|
|
|
|
|
|
|
class Reference(Extension): |
|
|
|
|
|
""" |
|
|
|
|
|
Marks a block in a template such that its available for use |
|
|
|
|
|
when referenced using a `refer` tag. |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
tags = set(['mark', 'reference']) |
|
|
|
|
|
|
|
|
|
|
|
def parse(self, parser): |
|
|
|
|
|
""" |
|
|
|
|
|
Parse the variable name that the content must be assigned to. |
|
|
|
|
|
""" |
|
|
|
|
|
token = parser.stream.next() |
|
|
|
|
|
lineno = token.lineno |
|
|
|
|
|
tag = token.value |
|
|
|
|
|
name = parser.stream.next().value |
|
|
|
|
|
body = parser.parse_statements(['name:end%s' % tag], drop_needle=True) |
|
|
|
|
|
return nodes.CallBlock( |
|
|
|
|
|
self.call_method('_render_output', |
|
|
|
|
|
args=[nodes.Name(MARKINGS, 'load'), nodes.Const(name)]), |
|
|
|
|
|
[], [], body).set_lineno(lineno) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _render_output(self, markings, name, caller=None): |
|
|
|
|
|
if not caller: |
|
|
|
|
|
return '' |
|
|
|
|
|
out = caller() |
|
|
|
|
|
if isinstance(markings, dict): |
|
|
|
|
|
markings[name] = out |
|
|
|
|
|
return out |
|
|
|
|
|
|
|
|
|
|
|
class Refer(Extension): |
|
|
|
|
|
""" |
|
|
|
|
|
Imports content blocks specified in the referred template as |
|
|
|
|
|
variables in a given namespace. |
|
|
|
|
|
""" |
|
|
|
|
|
tags = set(['refer']) |
|
|
|
|
|
|
|
|
|
|
|
def parse(self, parser): |
|
|
|
|
|
""" |
|
|
|
|
|
Parse the referred template and the namespace. |
|
|
|
|
|
""" |
|
|
|
|
|
token = parser.stream.next() |
|
|
|
|
|
lineno = token.lineno |
|
|
|
|
|
tag = token.value |
|
|
|
|
|
parser.stream.expect('name:to') |
|
|
|
|
|
template = parser.parse_expression() |
|
|
|
|
|
parser.stream.expect('name:as') |
|
|
|
|
|
namespace = parser.stream.next().value |
|
|
|
|
|
includeNode = nodes.Include(lineno=lineno) |
|
|
|
|
|
includeNode.with_context = True |
|
|
|
|
|
includeNode.ignore_missing = False |
|
|
|
|
|
includeNode.template = template |
|
|
|
|
|
return [ |
|
|
|
|
|
nodes.Assign(nodes.Name(MARKINGS, 'store'), nodes.Const({})), |
|
|
|
|
|
nodes.Assign(nodes.Name(namespace, 'store'), nodes.Const({})), |
|
|
|
|
|
nodes.CallBlock( |
|
|
|
|
|
self.call_method('_assign_reference', |
|
|
|
|
|
args=[ |
|
|
|
|
|
nodes.Name(MARKINGS, 'load'), |
|
|
|
|
|
nodes.Name(namespace, 'load')]), |
|
|
|
|
|
[], [], [includeNode]).set_lineno(lineno)] |
|
|
|
|
|
|
|
|
|
|
|
def _assign_reference(self, markings, namespace, caller): |
|
|
|
|
|
""" |
|
|
|
|
|
Assign the processed variables into the |
|
|
|
|
|
given namespace. |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
out = caller() |
|
|
|
|
|
for key, value in markings.items(): |
|
|
|
|
|
namespace[key] = value |
|
|
|
|
|
namespace['html'] = HtmlWrap(out) |
|
|
|
|
|
return '' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HydeLoader(FileSystemLoader): |
|
|
class HydeLoader(FileSystemLoader): |
|
|
|
|
|
""" |
|
|
|
|
|
A wrapper around the file system loader that performs |
|
|
|
|
|
hyde specific tweaks. |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
def __init__(self, sitepath, site, preprocessor=None): |
|
|
def __init__(self, sitepath, site, preprocessor=None): |
|
|
config = site.config if hasattr(site, 'config') else None |
|
|
config = site.config if hasattr(site, 'config') else None |
|
|
@@ -101,6 +211,9 @@ class HydeLoader(FileSystemLoader): |
|
|
self.preprocessor = preprocessor |
|
|
self.preprocessor = preprocessor |
|
|
|
|
|
|
|
|
def get_source(self, environment, template): |
|
|
def get_source(self, environment, template): |
|
|
|
|
|
""" |
|
|
|
|
|
Calls the plugins to preprocess prior to returning the source. |
|
|
|
|
|
""" |
|
|
(contents, |
|
|
(contents, |
|
|
filename, |
|
|
filename, |
|
|
date) = super(HydeLoader, self).get_source( |
|
|
date) = super(HydeLoader, self).get_source( |
|
|
@@ -121,22 +234,29 @@ class Jinja2Template(Template): |
|
|
def __init__(self, sitepath): |
|
|
def __init__(self, sitepath): |
|
|
super(Jinja2Template, self).__init__(sitepath) |
|
|
super(Jinja2Template, self).__init__(sitepath) |
|
|
|
|
|
|
|
|
def configure(self, site, preprocessor=None, postprocessor=None): |
|
|
|
|
|
|
|
|
def configure(self, site, engine=None): |
|
|
""" |
|
|
""" |
|
|
Uses the site object to initialize the jinja environment. |
|
|
Uses the site object to initialize the jinja environment. |
|
|
""" |
|
|
""" |
|
|
self.site = site |
|
|
self.site = site |
|
|
|
|
|
self.engine = engine |
|
|
|
|
|
preprocessor = (engine.preprocessor |
|
|
|
|
|
if hasattr(engine, 'preprocessor') else None) |
|
|
|
|
|
|
|
|
self.loader = HydeLoader(self.sitepath, site, preprocessor) |
|
|
self.loader = HydeLoader(self.sitepath, site, preprocessor) |
|
|
self.env = Environment(loader=self.loader, |
|
|
self.env = Environment(loader=self.loader, |
|
|
undefined=SilentUndefined, |
|
|
undefined=SilentUndefined, |
|
|
trim_blocks=True, |
|
|
trim_blocks=True, |
|
|
extensions=[IncludeText, |
|
|
extensions=[IncludeText, |
|
|
Markdown, |
|
|
Markdown, |
|
|
|
|
|
Reference, |
|
|
|
|
|
Refer, |
|
|
'jinja2.ext.do', |
|
|
'jinja2.ext.do', |
|
|
'jinja2.ext.loopcontrols', |
|
|
'jinja2.ext.loopcontrols', |
|
|
'jinja2.ext.with_']) |
|
|
'jinja2.ext.with_']) |
|
|
self.env.globals['media_url'] = media_url |
|
|
self.env.globals['media_url'] = media_url |
|
|
self.env.globals['content_url'] = content_url |
|
|
self.env.globals['content_url'] = content_url |
|
|
|
|
|
self.env.globals['engine'] = engine |
|
|
self.env.filters['markdown'] = markdown |
|
|
self.env.filters['markdown'] = markdown |
|
|
|
|
|
|
|
|
config = {} |
|
|
config = {} |
|
|
@@ -171,6 +291,9 @@ class Jinja2Template(Template): |
|
|
|
|
|
|
|
|
@property |
|
|
@property |
|
|
def exception_class(self): |
|
|
def exception_class(self): |
|
|
|
|
|
""" |
|
|
|
|
|
The exception to throw. Used by plugins. |
|
|
|
|
|
""" |
|
|
return TemplateError |
|
|
return TemplateError |
|
|
|
|
|
|
|
|
def render(self, text, context): |
|
|
def render(self, text, context): |
|
|
|