PageRenderTime 49ms CodeModel.GetById 2ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/hyde/ext/templates/jinja.py

http://github.com/hyde/hyde
Python | 899 lines | 808 code | 52 blank | 39 comment | 33 complexity | b67568f006fee18816331f0b5d2b550d MD5 | raw file
  1# -*- coding: utf-8 -*-
  2"""
  3Jinja template utilties
  4"""
  5
  6from datetime import datetime, date
  7import itertools
  8import os
  9import re
 10import sys
 11
 12from hyde._compat import PY3, quote, unquote, str, StringIO
 13from hyde.exceptions import HydeException
 14from hyde.model import Expando
 15from hyde.template import HtmlWrap, Template
 16from operator import attrgetter
 17
 18from jinja2 import (
 19    contextfunction,
 20    Environment,
 21    FileSystemLoader,
 22    FileSystemBytecodeCache
 23)
 24from jinja2 import contextfilter, environmentfilter, Markup, Undefined, nodes
 25from jinja2.ext import Extension
 26from jinja2.exceptions import TemplateError
 27
 28from commando.util import getLoggerWithNullHandler
 29
 30logger = getLoggerWithNullHandler('hyde.engine.Jinja2')
 31
 32
 33class SilentUndefined(Undefined):
 34
 35    """
 36    A redefinition of undefined that eats errors.
 37    """
 38
 39    def __getattr__(self, name):
 40        return self
 41
 42    __getitem__ = __getattr__
 43
 44    def __call__(self, *args, **kwargs):
 45        return self
 46
 47
 48@contextfunction
 49def media_url(context, path, safe=None):
 50    """
 51    Returns the media url given a partial path.
 52    """
 53    return context['site'].media_url(path, safe)
 54
 55
 56@contextfunction
 57def content_url(context, path, safe=None):
 58    """
 59    Returns the content url given a partial path.
 60    """
 61    return context['site'].content_url(path, safe)
 62
 63
 64@contextfunction
 65def full_url(context, path, safe=None):
 66    """
 67    Returns the full url given a partial path.
 68    """
 69    return context['site'].full_url(path, safe)
 70
 71
 72@contextfilter
 73def urlencode(ctx, url, safe=None):
 74    if safe is not None:
 75        return quote(url.encode('utf8'), safe)
 76    else:
 77        return quote(url.encode('utf8'))
 78
 79
 80@contextfilter
 81def urldecode(ctx, url):
 82    url = unquote(url)
 83    if not PY3:
 84        url = url.decode('utf8')
 85    return url
 86
 87
 88@contextfilter
 89def date_format(ctx, dt, fmt=None):
 90    if not dt:
 91        dt = datetime.now()
 92    if not isinstance(dt, datetime) or \
 93            not isinstance(dt, date):
 94        logger.error("Date format called on a non date object")
 95        return dt
 96
 97    format = fmt or "%a, %d %b %Y"
 98    if not fmt:
 99        global_format = ctx.resolve('dateformat')
100        if not isinstance(global_format, Undefined):
101            format = global_format
102    return dt.strftime(format)
103
104
105def islice(iterable, start=0, stop=3, step=1):
106    return itertools.islice(iterable, start, stop, step)
107
108
109def top(iterable, count=3):
110    return islice(iterable, stop=count)
111
112
113def xmldatetime(dt):
114    if not dt:
115        dt = datetime.now()
116    zprefix = "Z"
117    tz = dt.strftime("%z")
118    if tz:
119        zprefix = tz[:3] + ":" + tz[3:]
120    return dt.strftime("%Y-%m-%dT%H:%M:%S") + zprefix
121
122
123@environmentfilter
124def asciidoc(env, value):
125    """
126    (simple) Asciidoc filter
127    """
128    try:
129        from asciidocapi import AsciiDocAPI
130    except ImportError:
131        print(u"Requires AsciiDoc library to use AsciiDoc tag.")
132        raise
133
134    output = value
135
136    asciidoc = AsciiDocAPI()
137    asciidoc.options('--no-header-footer')
138    result = StringIO()
139    asciidoc.execute(
140        StringIO(output.encode('utf-8')), result, backend='html4')
141    return str(result.getvalue(), "utf-8")
142
143
144@environmentfilter
145def markdown(env, value):
146    """
147    Markdown filter with support for extensions.
148    """
149    try:
150        import markdown as md
151    except ImportError:
152        logger.error(u"Cannot load the markdown library.")
153        raise TemplateError(u"Cannot load the markdown library")
154    output = value
155    d = {}
156    if hasattr(env.config, 'markdown'):
157        d['extensions'] = getattr(env.config.markdown, 'extensions', [])
158        d['extension_configs'] = getattr(env.config.markdown,
159                                         'extension_configs',
160                                         Expando({})).to_dict()
161        if hasattr(env.config.markdown, 'output_format'):
162            d['output_format'] = env.config.markdown.output_format
163    marked = md.Markdown(**d)
164
165    return marked.convert(output)
166
167
168@environmentfilter
169def restructuredtext(env, value):
170    """
171    RestructuredText filter
172    """
173    try:
174        from docutils.core import publish_parts
175    except ImportError:
176        logger.error(u"Cannot load the docutils library.")
177        raise TemplateError(u"Cannot load the docutils library.")
178
179    highlight_source = False
180    if hasattr(env.config, 'restructuredtext'):
181        highlight_source = getattr(
182            env.config.restructuredtext, 'highlight_source', False)
183        extensions = getattr(env.config.restructuredtext, 'extensions', [])
184        import imp
185        for extension in extensions:
186            imp.load_module(extension, *imp.find_module(extension))
187
188    if highlight_source:
189        import hyde.lib.pygments.rst_directive  # noqa
190
191    parts = publish_parts(source=value, writer_name="html")
192    return parts['html_body']
193
194
195@environmentfilter
196def syntax(env, value, lexer=None, filename=None):
197    """
198    Processes the contained block using `pygments`
199    """
200    try:
201        import pygments
202        from pygments import lexers
203        from pygments import formatters
204    except ImportError:
205        logger.error(u"pygments library is required to"
206                     " use syntax highlighting tags.")
207        raise TemplateError("Cannot load pygments")
208
209    pyg = (lexers.get_lexer_by_name(lexer)
210           if lexer else
211           lexers.guess_lexer(value))
212    settings = {}
213    if hasattr(env.config, 'syntax'):
214        settings = getattr(env.config.syntax,
215                           'options',
216                           Expando({})).to_dict()
217
218    formatter = formatters.HtmlFormatter(**settings)
219    code = pygments.highlight(value, pyg, formatter)
220    code = code.replace('\n\n', '\n&nbsp;\n').replace('\n', '<br />')
221    caption = filename if filename else pyg.name
222    if hasattr(env.config, 'syntax'):
223        if not getattr(env.config.syntax, 'use_figure', True):
224            return Markup(code)
225    return Markup(
226        '<div class="codebox"><figure class="code">%s<figcaption>'
227        '%s</figcaption></figure></div>\n\n'
228        % (code, caption))
229
230
231class Spaceless(Extension):
232
233    """
234    Emulates the django spaceless template tag.
235    """
236
237    tags = set(['spaceless'])
238
239    def parse(self, parser):
240        """
241        Parses the statements and calls back to strip spaces.
242        """
243        lineno = next(parser.stream).lineno
244        body = parser.parse_statements(['name:endspaceless'],
245                                       drop_needle=True)
246        return nodes.CallBlock(
247            self.call_method('_render_spaceless'),
248            [], [], body).set_lineno(lineno)
249
250    def _render_spaceless(self, caller=None):
251        """
252        Strip the spaces between tags using the regular expression
253        from django. Stolen from `django.util.html` Returns the given HTML
254        with spaces between tags removed.
255        """
256        if not caller:
257            return ''
258        return re.sub(r'>\s+<', '><', str(caller().strip()))
259
260
261class Asciidoc(Extension):
262
263    """
264    A wrapper around the asciidoc filter for syntactic sugar.
265    """
266    tags = set(['asciidoc'])
267
268    def parse(self, parser):
269        """
270        Parses the statements and defers to the callback
271        for asciidoc processing.
272        """
273        lineno = next(parser.stream).lineno
274        body = parser.parse_statements(['name:endasciidoc'], drop_needle=True)
275
276        return nodes.CallBlock(
277            self.call_method('_render_asciidoc'),
278            [], [], body).set_lineno(lineno)
279
280    def _render_asciidoc(self, caller=None):
281        """
282        Calls the asciidoc filter to transform the output.
283        """
284        if not caller:
285            return ''
286        output = caller().strip()
287        return asciidoc(self.environment, output)
288
289
290class Markdown(Extension):
291
292    """
293    A wrapper around the markdown filter for syntactic sugar.
294    """
295    tags = set(['markdown'])
296
297    def parse(self, parser):
298        """
299        Parses the statements and defers to the callback
300        for markdown processing.
301        """
302        lineno = next(parser.stream).lineno
303        body = parser.parse_statements(['name:endmarkdown'], drop_needle=True)
304
305        return nodes.CallBlock(
306            self.call_method('_render_markdown'),
307            [], [], body).set_lineno(lineno)
308
309    def _render_markdown(self, caller=None):
310        """
311        Calls the markdown filter to transform the output.
312        """
313        if not caller:
314            return ''
315        output = caller().strip()
316        return markdown(self.environment, output)
317
318
319class restructuredText(Extension):
320
321    """
322    A wrapper around the restructuredtext filter for syntactic sugar
323    """
324    tags = set(['restructuredtext'])
325
326    def parse(self, parser):
327        """
328        Simply extract our content
329        """
330        lineno = next(parser.stream).lineno
331        body = parser.parse_statements(
332            ['name:endrestructuredtext'], drop_needle=True)
333
334        return nodes.CallBlock(self.call_method('_render_rst'), [],  [], body
335                               ).set_lineno(lineno)
336
337    def _render_rst(self, caller=None):
338        """
339        call our restructuredtext filter
340        """
341        if not caller:
342            return ''
343        output = caller().strip()
344        return restructuredtext(self.environment, output)
345
346
347class YamlVar(Extension):
348
349    """
350    An extension that converts the content between the tags
351    into an yaml object and sets the value in the given
352    variable.
353    """
354
355    tags = set(['yaml'])
356
357    def parse(self, parser):
358        """
359        Parses the contained data and defers to the callback to load it as
360        yaml.
361        """
362        lineno = next(parser.stream).lineno
363        var = parser.stream.expect('name').value
364        body = parser.parse_statements(['name:endyaml'], drop_needle=True)
365        return [
366            nodes.Assign(
367                nodes.Name(var, 'store'),
368                nodes.Const({})
369            ).set_lineno(lineno),
370            nodes.CallBlock(
371                self.call_method('_set_yaml',
372                                 args=[nodes.Name(var, 'load')]),
373                [], [], body).set_lineno(lineno)
374        ]
375
376    def _set_yaml(self, var, caller=None):
377        """
378        Loads the yaml data into the specified variable.
379        """
380        if not caller:
381            return ''
382        try:
383            import yaml
384        except ImportError:
385            return ''
386
387        out = caller().strip()
388        var.update(yaml.load(out))
389        return ''
390
391
392def parse_kwargs(parser):
393    """
394    Parses keyword arguments in tags.
395    """
396    name = parser.stream.expect('name').value
397    parser.stream.expect('assign')
398    if parser.stream.current.test('string'):
399        value = parser.parse_expression()
400    else:
401        value = nodes.Const(next(parser.stream).value)
402    return (name, value)
403
404
405class Syntax(Extension):
406
407    """
408    A wrapper around the syntax filter for syntactic sugar.
409    """
410
411    tags = set(['syntax'])
412
413    def parse(self, parser):
414        """
415        Parses the statements and defers to the callback for
416        pygments processing.
417        """
418        lineno = next(parser.stream).lineno
419        lex = nodes.Const(None)
420        filename = nodes.Const(None)
421
422        if not parser.stream.current.test('block_end'):
423            if parser.stream.look().test('assign'):
424                name = value = value1 = None
425                (name, value) = parse_kwargs(parser)
426                if parser.stream.skip_if('comma'):
427                    (_, value1) = parse_kwargs(parser)
428
429                (lex, filename) = (value, value1) \
430                    if name == 'lex' \
431                    else (value1, value)
432            else:
433                lex = nodes.Const(next(parser.stream).value)
434                if parser.stream.skip_if('comma'):
435                    filename = parser.parse_expression()
436
437        body = parser.parse_statements(['name:endsyntax'], drop_needle=True)
438        return nodes.CallBlock(
439            self.call_method('_render_syntax',
440                             args=[lex, filename]),
441            [], [], body).set_lineno(lineno)
442
443    def _render_syntax(self, lex, filename, caller=None):
444        """
445        Calls the syntax filter to transform the output.
446        """
447        if not caller:
448            return ''
449        output = caller().strip()
450        return syntax(self.environment, output, lex, filename)
451
452
453class IncludeText(Extension):
454
455    """
456    Automatically runs `markdown` and `typogrify` on included
457    files.
458    """
459
460    tags = set(['includetext'])
461
462    def parse(self, parser):
463        """
464        Delegates all the parsing to the native include node.
465        """
466        node = parser.parse_include()
467        return nodes.CallBlock(
468            self.call_method('_render_include_text'),
469            [], [], [node]).set_lineno(node.lineno)
470
471    def _render_include_text(self, caller=None):
472        """
473        Runs markdown and if available, typogrigy on the
474        content returned by the include node.
475        """
476        if not caller:
477            return ''
478        output = caller().strip()
479        output = markdown(self.environment, output)
480        if 'typogrify' in self.environment.filters:
481            typo = self.environment.filters['typogrify']
482            output = typo(output)
483        return output
484
485MARKINGS = '_markings_'
486
487
488class Reference(Extension):
489
490    """
491    Marks a block in a template such that its available for use
492    when referenced using a `refer` tag.
493    """
494
495    tags = set(['mark', 'reference'])
496
497    def parse(self, parser):
498        """
499        Parse the variable name that the content must be assigned to.
500        """
501        token = next(parser.stream)
502        lineno = token.lineno
503        tag = token.value
504        name = next(parser.stream).value
505        body = parser.parse_statements(['name:end%s' % tag], drop_needle=True)
506        return nodes.CallBlock(self.call_method('_render_output',
507                                                args=[
508                                                    nodes.Name(MARKINGS,
509                                                               'load'),
510                                                    nodes.Const(name)
511                                                ]), [], [],
512                               body).set_lineno(lineno)
513
514    def _render_output(self, markings, name, caller=None):
515        """
516        Assigns the result of the contents to the markings variable.
517        """
518        if not caller:
519            return ''
520        out = caller()
521        if isinstance(markings, dict):
522            markings[name] = out
523        return out
524
525
526class Refer(Extension):
527
528    """
529    Imports content blocks specified in the referred template as
530    variables in a given namespace.
531    """
532    tags = set(['refer'])
533
534    def parse(self, parser):
535        """
536        Parse the referred template and the namespace.
537        """
538        token = next(parser.stream)
539        lineno = token.lineno
540        parser.stream.expect('name:to')
541        template = parser.parse_expression()
542        parser.stream.expect('name:as')
543        namespace = next(parser.stream).value
544        includeNode = nodes.Include(lineno=lineno)
545        includeNode.with_context = True
546        includeNode.ignore_missing = False
547        includeNode.template = template
548
549        temp = parser.free_identifier(lineno)
550
551        return [
552            nodes.Assign(
553                nodes.Name(temp.name, 'store'),
554                nodes.Name(MARKINGS, 'load')
555            ).set_lineno(lineno),
556            nodes.Assign(
557                nodes.Name(MARKINGS, 'store'),
558                nodes.Const({})).set_lineno(lineno),
559            nodes.Assign(
560                nodes.Name(namespace, 'store'),
561                nodes.Const({})).set_lineno(lineno),
562            nodes.CallBlock(
563                self.call_method('_push_resource',
564                                 args=[
565                                     nodes.Name(namespace, 'load'),
566                                     nodes.Name('site', 'load'),
567                                     nodes.Name('resource', 'load'),
568                                     template]),
569                [], [], []).set_lineno(lineno),
570            nodes.Assign(
571                nodes.Name('resource', 'store'),
572                nodes.Getitem(nodes.Name(namespace, 'load'),
573                              nodes.Const('resource'), 'load')
574            ).set_lineno(lineno),
575            nodes.CallBlock(
576                self.call_method('_assign_reference',
577                                 args=[
578                                     nodes.Name(MARKINGS, 'load'),
579                                     nodes.Name(namespace, 'load')]),
580                [], [], [includeNode]).set_lineno(lineno),
581            nodes.Assign(nodes.Name('resource', 'store'),
582                         nodes.Getitem(nodes.Name(namespace, 'load'),
583                                       nodes.Const('parent_resource'), 'load')
584                         ).set_lineno(lineno),
585            nodes.Assign(
586                nodes.Name(MARKINGS, 'store'),
587                nodes.Name(temp.name, 'load')
588            ).set_lineno(lineno),
589        ]
590
591    def _push_resource(self, namespace, site, resource, template, caller):
592        """
593        Saves the current references in a stack.
594        """
595        namespace['parent_resource'] = resource
596        if not hasattr(resource, 'depends'):
597            resource.depends = []
598        if template not in resource.depends:
599            resource.depends.append(template)
600        namespace['resource'] = site.content.resource_from_relative_path(
601            template)
602        return ''
603
604    def _assign_reference(self, markings, namespace, caller):
605        """
606        Assign the processed variables into the
607        given namespace.
608        """
609
610        out = caller()
611        for key, value in markings.items():
612            namespace[key] = value
613        namespace['html'] = HtmlWrap(out)
614        return ''
615
616
617class HydeLoader(FileSystemLoader):
618
619    """
620    A wrapper around the file system loader that performs
621    hyde specific tweaks.
622    """
623
624    def __init__(self, sitepath, site, preprocessor=None):
625        config = site.config if hasattr(site, 'config') else None
626        if config:
627            super(HydeLoader, self).__init__([
628                str(config.content_root_path),
629                str(config.layout_root_path),
630            ])
631        else:
632            super(HydeLoader, self).__init__(str(sitepath))
633
634        self.site = site
635        self.preprocessor = preprocessor
636
637    def get_source(self, environment, template):
638        """
639        Calls the plugins to preprocess prior to returning the source.
640        """
641        template = template.strip()
642        # Fixed so that jinja2 loader does not have issues with
643        # seprator in windows
644        #
645        template = template.replace(os.sep, '/')
646        logger.debug("Loading template [%s] and preprocessing" % template)
647        try:
648            (contents,
649                filename,
650             date) = super(HydeLoader, self).get_source(
651                environment, template)
652        except UnicodeDecodeError:
653            HydeException.reraise(
654                "Unicode error when processing %s" % template, sys.exc_info())
655        except TemplateError as exc:
656            HydeException.reraise('Error when processing %s: %s' % (
657                template,
658                str(exc)
659            ), sys.exc_info())
660
661        if self.preprocessor:
662            resource = self.site.content.resource_from_relative_path(template)
663            if resource:
664                contents = self.preprocessor(resource, contents) or contents
665        return (contents, filename, date)
666
667
668# pylint: disable-msg=W0104,E0602,W0613,R0201
669class Jinja2Template(Template):
670
671    """
672    The Jinja2 Template implementation
673    """
674
675    def __init__(self, sitepath):
676        super(Jinja2Template, self).__init__(sitepath)
677
678    def configure(self, site, engine=None):
679        """
680        Uses the site object to initialize the jinja environment.
681        """
682        self.site = site
683        self.engine = engine
684        self.preprocessor = (engine.preprocessor
685                             if hasattr(engine, 'preprocessor') else None)
686
687        self.loader = HydeLoader(self.sitepath, site, self.preprocessor)
688
689        default_extensions = [
690            IncludeText,
691            Spaceless,
692            Asciidoc,
693            Markdown,
694            restructuredText,
695            Syntax,
696            Reference,
697            Refer,
698            YamlVar,
699            'jinja2.ext.do',
700            'jinja2.ext.loopcontrols',
701            'jinja2.ext.with_'
702        ]
703
704        defaults = {
705            'line_statement_prefix': '$$$',
706            'trim_blocks': True,
707        }
708
709        settings = dict()
710        settings.update(defaults)
711        settings['extensions'] = list()
712        settings['extensions'].extend(default_extensions)
713        settings['filters'] = {}
714        settings['tests'] = {}
715
716        conf = {}
717
718        try:
719            conf = attrgetter('config.jinja2')(site).to_dict()
720        except AttributeError:
721            pass
722
723        settings.update(
724            dict([(key, conf[key]) for key in defaults if key in conf]))
725
726        extensions = conf.get('extensions', [])
727        if isinstance(extensions, list):
728            settings['extensions'].extend(extensions)
729        else:
730            settings['extensions'].append(extensions)
731
732        filters = conf.get('filters', {})
733        if isinstance(filters, dict):
734            for name, value in filters.items():
735                parts = value.split('.')
736                module_name = '.'.join(parts[:-1])
737                function_name = parts[-1]
738                module = __import__(module_name, fromlist=[function_name])
739                settings['filters'][name] = getattr(module, function_name)
740
741        tests = conf.get('tests', {})
742        if isinstance(tests, dict):
743            for name, value in tests.items():
744                parts = value.split('.')
745                module_name = '.'.join(parts[:-1])
746                function_name = parts[-1]
747                module = __import__(module_name, fromlist=[function_name])
748                settings['tests'][name] = getattr(module, function_name)
749
750        self.env = Environment(
751            loader=self.loader,
752            undefined=SilentUndefined,
753            line_statement_prefix=settings['line_statement_prefix'],
754            trim_blocks=True,
755            bytecode_cache=FileSystemBytecodeCache(),
756            extensions=settings['extensions'])
757        self.env.globals['media_url'] = media_url
758        self.env.globals['content_url'] = content_url
759        self.env.globals['full_url'] = full_url
760        self.env.globals['engine'] = engine
761        self.env.globals['deps'] = {}
762        self.env.filters['urlencode'] = urlencode
763        self.env.filters['urldecode'] = urldecode
764        self.env.filters['asciidoc'] = asciidoc
765        self.env.filters['markdown'] = markdown
766        self.env.filters['restructuredtext'] = restructuredtext
767        self.env.filters['syntax'] = syntax
768        self.env.filters['date_format'] = date_format
769        self.env.filters['xmldatetime'] = xmldatetime
770        self.env.filters['islice'] = islice
771        self.env.filters['top'] = top
772        self.env.filters.update(settings['filters'])
773        self.env.tests.update(settings['tests'])
774
775        config = {}
776        if hasattr(site, 'config'):
777            config = site.config
778
779        self.env.extend(config=config)
780
781        try:
782            from typogrify.templatetags import jinja_filters
783        except ImportError:
784            jinja_filters = False
785
786        if jinja_filters:
787            jinja_filters.register(self.env)
788
789    def clear_caches(self):
790        """
791        Clear all caches to prepare for regeneration
792        """
793        if self.env.bytecode_cache:
794            self.env.bytecode_cache.clear()
795
796    def get_dependencies(self, path):
797        """
798        Finds dependencies hierarchically based on the included
799        files.
800        """
801        text = self.env.loader.get_source(self.env, path)[0]
802        from jinja2.meta import find_referenced_templates
803        try:
804            ast = self.env.parse(text)
805        except Exception as e:
806            HydeException.reraise(
807                "Error processing %s: \n%s" % (path, str(e)),
808                sys.exc_info())
809
810        tpls = find_referenced_templates(ast)
811        deps = list(self.env.globals['deps'].get('path', []))
812        for dep in tpls:
813            deps.append(dep)
814            if dep:
815                deps.extend(self.get_dependencies(dep))
816        return list(set(deps))
817
818    @property
819    def exception_class(self):
820        """
821        The exception to throw. Used by plugins.
822        """
823        return TemplateError
824
825    @property
826    def patterns(self):
827        """
828        The pattern for matching selected template statements.
829        """
830        return {
831            "block_open": '\s*\{\%\s*block\s*([^\s]+)\s*\%\}',
832            "block_close": '\s*\{\%\s*endblock\s*([^\s]*)\s*\%\}',
833            "include":
834                '\s*\{\%\s*include\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}',
835            "extends":
836                '\s*\{\%\s*extends\s*(?:\'|\")(.+?\.[^.]*)(?:\'|\")\s*\%\}'
837        }
838
839    def get_include_statement(self, path_to_include):
840        """
841        Returns an include statement for the current template,
842        given the path to include.
843        """
844        return '{%% include \'%s\' %%}' % path_to_include
845
846    def get_extends_statement(self, path_to_extend):
847        """
848        Returns an extends statement for the current template,
849        given the path to extend.
850        """
851        return '{%% extends \'%s\' %%}' % path_to_extend
852
853    def get_open_tag(self, tag, params):
854        """
855        Returns an open tag statement.
856        """
857        return '{%% %s %s %%}' % (tag, params)
858
859    def get_close_tag(self, tag, params):
860        """
861        Returns an open tag statement.
862        """
863        return '{%% end%s %%}' % tag
864
865    def get_content_url_statement(self, url):
866        """
867        Returns the content url statement.
868        """
869        return '{{ content_url(\'%s\') }}' % url
870
871    def get_media_url_statement(self, url):
872        """
873        Returns the media url statement.
874        """
875        return '{{ media_url(\'%s\') }}' % url
876
877    def get_full_url_statement(self, url):
878        """
879        Returns the full url statement.
880        """
881        return '{{ full_url(\'%s\') }}' % url
882
883    def render_resource(self, resource, context):
884        """
885        Renders the given resource using the context
886        """
887        try:
888            template = self.env.get_template(resource.relative_path)
889            out = template.render(context)
890        except:
891            raise
892        return out
893
894    def render(self, text, context):
895        """
896        Renders the given text using the context
897        """
898        template = self.env.from_string(text)
899        return template.render(context)