A fork of hyde, the static site generation. Some patches will be pushed upstream.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

887 lines
26 KiB

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