/hyde/ext/templates/jinja.py

http://github.com/hyde/hyde · Python · 899 lines · 561 code · 151 blank · 187 comment · 86 complexity · b67568f006fee18816331f0b5d2b550d MD5 · raw file

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