PageRenderTime 55ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/gluon/template.py

https://github.com/gokceneraslan/web2py
Python | 918 lines | 879 code | 15 blank | 24 comment | 14 complexity | 39ba9d98aa9836613657410e688efe40 MD5 | raw file
Possible License(s): BSD-2-Clause, MIT, BSD-3-Clause
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
  5. License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
  6. Author: Thadeus Burgess
  7. Contributors:
  8. - Thank you to Massimo Di Pierro for creating the original gluon/template.py
  9. - Thank you to Jonathan Lundell for extensively testing the regex on Jython.
  10. - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
  11. """
  12. import os
  13. import cgi
  14. import logging
  15. from re import compile, sub, escape, DOTALL
  16. try:
  17. import cStringIO as StringIO
  18. except:
  19. from io import StringIO
  20. try:
  21. # have web2py
  22. from restricted import RestrictedError
  23. from globals import current
  24. except ImportError:
  25. # do not have web2py
  26. current = None
  27. def RestrictedError(a, b, c):
  28. logging.error(str(a) + ':' + str(b) + ':' + str(c))
  29. return RuntimeError
  30. class Node(object):
  31. """
  32. Basic Container Object
  33. """
  34. def __init__(self, value=None, pre_extend=False):
  35. self.value = value
  36. self.pre_extend = pre_extend
  37. def __str__(self):
  38. return str(self.value)
  39. class SuperNode(Node):
  40. def __init__(self, name='', pre_extend=False):
  41. self.name = name
  42. self.value = None
  43. self.pre_extend = pre_extend
  44. def __str__(self):
  45. if self.value:
  46. return str(self.value)
  47. else:
  48. # raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
  49. return ''
  50. def __repr__(self):
  51. return "%s->%s" % (self.name, self.value)
  52. def output_aux(node, blocks):
  53. # If we have a block level
  54. # If we can override this block.
  55. # Override block from vars.
  56. # Else we take the default
  57. # Else its just a string
  58. return (blocks[node.name].output(blocks)
  59. if node.name in blocks else
  60. node.output(blocks)) \
  61. if isinstance(node, BlockNode) \
  62. else str(node)
  63. class BlockNode(Node):
  64. """
  65. Block Container.
  66. This Node can contain other Nodes and will render in a hierarchical order
  67. of when nodes were added.
  68. ie::
  69. {{ block test }}
  70. This is default block test
  71. {{ end }}
  72. """
  73. def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
  74. """
  75. name - Name of this Node.
  76. """
  77. self.nodes = []
  78. self.name = name
  79. self.pre_extend = pre_extend
  80. self.left, self.right = delimiters
  81. def __repr__(self):
  82. lines = ['%sblock %s%s' % (self.left, self.name, self.right)]
  83. lines += [str(node) for node in self.nodes]
  84. lines.append('%send%s' % (self.left, self.right))
  85. return ''.join(lines)
  86. def __str__(self):
  87. """
  88. Get this BlockNodes content, not including child Nodes
  89. """
  90. return ''.join(str(node) for node in self.nodes
  91. if not isinstance(node, BlockNode))
  92. def append(self, node):
  93. """
  94. Add an element to the nodes.
  95. Keyword Arguments
  96. - node -- Node object or string to append.
  97. """
  98. if isinstance(node, str) or isinstance(node, Node):
  99. self.nodes.append(node)
  100. else:
  101. raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
  102. def extend(self, other):
  103. """
  104. Extend the list of nodes with another BlockNode class.
  105. Keyword Arguments
  106. - other -- BlockNode or Content object to extend from.
  107. """
  108. if isinstance(other, BlockNode):
  109. self.nodes.extend(other.nodes)
  110. else:
  111. raise TypeError(
  112. "Invalid type; must be instance of ``BlockNode``. %s" % other)
  113. def output(self, blocks):
  114. """
  115. Merges all nodes into a single string.
  116. blocks -- Dictionary of blocks that are extending
  117. from this template.
  118. """
  119. return ''.join(output_aux(node, blocks) for node in self.nodes)
  120. class Content(BlockNode):
  121. """
  122. Parent Container -- Used as the root level BlockNode.
  123. Contains functions that operate as such.
  124. """
  125. def __init__(self, name="ContentBlock", pre_extend=False):
  126. """
  127. Keyword Arguments
  128. name -- Unique name for this BlockNode
  129. """
  130. self.name = name
  131. self.nodes = []
  132. self.blocks = {}
  133. self.pre_extend = pre_extend
  134. def __str__(self):
  135. return ''.join(output_aux(node, self.blocks) for node in self.nodes)
  136. def _insert(self, other, index=0):
  137. """
  138. Inserts object at index.
  139. """
  140. if isinstance(other, (str, Node)):
  141. self.nodes.insert(index, other)
  142. else:
  143. raise TypeError(
  144. "Invalid type, must be instance of ``str`` or ``Node``.")
  145. def insert(self, other, index=0):
  146. """
  147. Inserts object at index.
  148. You may pass a list of objects and have them inserted.
  149. """
  150. if isinstance(other, (list, tuple)):
  151. # Must reverse so the order stays the same.
  152. other.reverse()
  153. for item in other:
  154. self._insert(item, index)
  155. else:
  156. self._insert(other, index)
  157. def append(self, node):
  158. """
  159. Adds a node to list. If it is a BlockNode then we assign a block for it.
  160. """
  161. if isinstance(node, (str, Node)):
  162. self.nodes.append(node)
  163. if isinstance(node, BlockNode):
  164. self.blocks[node.name] = node
  165. else:
  166. raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
  167. def extend(self, other):
  168. """
  169. Extends the objects list of nodes with another objects nodes
  170. """
  171. if isinstance(other, BlockNode):
  172. self.nodes.extend(other.nodes)
  173. self.blocks.update(other.blocks)
  174. else:
  175. raise TypeError(
  176. "Invalid type; must be instance of ``BlockNode``. %s" % other)
  177. def clear_content(self):
  178. self.nodes = []
  179. class TemplateParser(object):
  180. default_delimiters = ('{{', '}}')
  181. r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
  182. r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
  183. # These are used for re-indentation.
  184. # Indent + 1
  185. re_block = compile('^(elif |else:|except:|except |finally:).*$', DOTALL)
  186. # Indent - 1
  187. re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
  188. # Indent - 1
  189. re_pass = compile('^pass( .*)?$', DOTALL)
  190. def __init__(self, text,
  191. name="ParserContainer",
  192. context=dict(),
  193. path='views/',
  194. writer='response.write',
  195. lexers={},
  196. delimiters=('{{', '}}'),
  197. _super_nodes = [],
  198. ):
  199. """
  200. text -- text to parse
  201. context -- context to parse in
  202. path -- folder path to templates
  203. writer -- string of writer class to use
  204. lexers -- dict of custom lexers to use.
  205. delimiters -- for example ('{{','}}')
  206. _super_nodes -- a list of nodes to check for inclusion
  207. this should only be set by "self.extend"
  208. It contains a list of SuperNodes from a child
  209. template that need to be handled.
  210. """
  211. # Keep a root level name.
  212. self.name = name
  213. # Raw text to start parsing.
  214. self.text = text
  215. # Writer to use (refer to the default for an example).
  216. # This will end up as
  217. # "%s(%s, escape=False)" % (self.writer, value)
  218. self.writer = writer
  219. # Dictionary of custom name lexers to use.
  220. if isinstance(lexers, dict):
  221. self.lexers = lexers
  222. else:
  223. self.lexers = {}
  224. # Path of templates
  225. self.path = path
  226. # Context for templates.
  227. self.context = context
  228. # allow optional alternative delimiters
  229. self.delimiters = delimiters
  230. if delimiters != self.default_delimiters:
  231. escaped_delimiters = (escape(delimiters[0]),
  232. escape(delimiters[1]))
  233. self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
  234. elif hasattr(context.get('response', None), 'delimiters'):
  235. if context['response'].delimiters != self.default_delimiters:
  236. escaped_delimiters = (
  237. escape(context['response'].delimiters[0]),
  238. escape(context['response'].delimiters[1]))
  239. self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
  240. DOTALL)
  241. # Create a root level Content that everything will go into.
  242. self.content = Content(name=name)
  243. # Stack will hold our current stack of nodes.
  244. # As we descend into a node, it will be added to the stack
  245. # And when we leave, it will be removed from the stack.
  246. # self.content should stay on the stack at all times.
  247. self.stack = [self.content]
  248. # This variable will hold a reference to every super block
  249. # that we come across in this template.
  250. self.super_nodes = []
  251. # This variable will hold a reference to the child
  252. # super nodes that need handling.
  253. self.child_super_nodes = _super_nodes
  254. # This variable will hold a reference to every block
  255. # that we come across in this template
  256. self.blocks = {}
  257. # Begin parsing.
  258. self.parse(text)
  259. def to_string(self):
  260. """
  261. Return the parsed template with correct indentation.
  262. Used to make it easier to port to python3.
  263. """
  264. return self.reindent(str(self.content))
  265. def __str__(self):
  266. "Make sure str works exactly the same as python 3"
  267. return self.to_string()
  268. def __unicode__(self):
  269. "Make sure str works exactly the same as python 3"
  270. return self.to_string()
  271. def reindent(self, text):
  272. """
  273. Reindents a string of unindented python code.
  274. """
  275. # Get each of our lines into an array.
  276. lines = text.split('\n')
  277. # Our new lines
  278. new_lines = []
  279. # Keeps track of how many indents we have.
  280. # Used for when we need to drop a level of indentation
  281. # only to reindent on the next line.
  282. credit = 0
  283. # Current indentation
  284. k = 0
  285. #################
  286. # THINGS TO KNOW
  287. #################
  288. # k += 1 means indent
  289. # k -= 1 means unindent
  290. # credit = 1 means unindent on the next line.
  291. for raw_line in lines:
  292. line = raw_line.strip()
  293. # ignore empty lines
  294. if not line:
  295. continue
  296. # If we have a line that contains python code that
  297. # should be unindented for this line of code.
  298. # and then reindented for the next line.
  299. if TemplateParser.re_block.match(line):
  300. k = k + credit - 1
  301. # We obviously can't have a negative indentation
  302. k = max(k, 0)
  303. # Add the indentation!
  304. new_lines.append(' ' * (4 * k) + line)
  305. # Bank account back to 0 again :(
  306. credit = 0
  307. # If we are a pass block, we obviously de-dent.
  308. if TemplateParser.re_pass.match(line):
  309. k -= 1
  310. # If we are any of the following, de-dent.
  311. # However, we should stay on the same level
  312. # But the line right after us will be de-dented.
  313. # So we add one credit to keep us at the level
  314. # while moving back one indentation level.
  315. if TemplateParser.re_unblock.match(line):
  316. credit = 1
  317. k -= 1
  318. # If we are an if statement, a try, or a semi-colon we
  319. # probably need to indent the next line.
  320. if line.endswith(':') and not line.startswith('#'):
  321. k += 1
  322. # This must come before so that we can raise an error with the
  323. # right content.
  324. new_text = '\n'.join(new_lines)
  325. if k > 0:
  326. self._raise_error('missing "pass" in view', new_text)
  327. elif k < 0:
  328. self._raise_error('too many "pass" in view', new_text)
  329. return new_text
  330. def _raise_error(self, message='', text=None):
  331. """
  332. Raise an error using itself as the filename and textual content.
  333. """
  334. raise RestrictedError(self.name, text or self.text, message)
  335. def _get_file_text(self, filename):
  336. """
  337. Attempt to open ``filename`` and retrieve its text.
  338. This will use self.path to search for the file.
  339. """
  340. # If they didn't specify a filename, how can we find one!
  341. if not filename.strip():
  342. self._raise_error('Invalid template filename')
  343. # Allow Views to include other views dynamically
  344. context = self.context
  345. if current and not "response" in context:
  346. context["response"] = getattr(current, 'response', None)
  347. # Get the filename; filename looks like ``"template.html"``.
  348. # We need to eval to remove the quotes and get the string type.
  349. filename = eval(filename, context)
  350. # Get the path of the file on the system.
  351. filepath = self.path and os.path.join(self.path, filename) or filename
  352. # try to read the text.
  353. try:
  354. fileobj = open(filepath, 'rb')
  355. text = fileobj.read()
  356. fileobj.close()
  357. except IOError:
  358. self._raise_error('Unable to open included view file: ' + filepath)
  359. return text
  360. def include(self, content, filename):
  361. """
  362. Include ``filename`` here.
  363. """
  364. text = self._get_file_text(filename)
  365. t = TemplateParser(text,
  366. name=filename,
  367. context=self.context,
  368. path=self.path,
  369. writer=self.writer,
  370. delimiters=self.delimiters)
  371. content.append(t.content)
  372. def extend(self, filename):
  373. """
  374. Extend ``filename``. Anything not declared in a block defined by the
  375. parent will be placed in the parent templates ``{{include}}`` block.
  376. """
  377. text = self._get_file_text(filename)
  378. # Create out nodes list to send to the parent
  379. super_nodes = []
  380. # We want to include any non-handled nodes.
  381. super_nodes.extend(self.child_super_nodes)
  382. # And our nodes as well.
  383. super_nodes.extend(self.super_nodes)
  384. t = TemplateParser(text,
  385. name=filename,
  386. context=self.context,
  387. path=self.path,
  388. writer=self.writer,
  389. delimiters=self.delimiters,
  390. _super_nodes=super_nodes)
  391. # Make a temporary buffer that is unique for parent
  392. # template.
  393. buf = BlockNode(
  394. name='__include__' + filename, delimiters=self.delimiters)
  395. pre = []
  396. # Iterate through each of our nodes
  397. for node in self.content.nodes:
  398. # If a node is a block
  399. if isinstance(node, BlockNode):
  400. # That happens to be in the parent template
  401. if node.name in t.content.blocks:
  402. # Do not include it
  403. continue
  404. if isinstance(node, Node):
  405. # Or if the node was before the extension
  406. # we should not include it
  407. if node.pre_extend:
  408. pre.append(node)
  409. continue
  410. # Otherwise, it should go int the
  411. # Parent templates {{include}} section.
  412. buf.append(node)
  413. else:
  414. buf.append(node)
  415. # Clear our current nodes. We will be replacing this with
  416. # the parent nodes.
  417. self.content.nodes = []
  418. t_content = t.content
  419. # Set our include, unique by filename
  420. t_content.blocks['__include__' + filename] = buf
  421. # Make sure our pre_extended nodes go first
  422. t_content.insert(pre)
  423. # Then we extend our blocks
  424. t_content.extend(self.content)
  425. # Work off the parent node.
  426. self.content = t_content
  427. def parse(self, text):
  428. # Basically, r_tag.split will split the text into
  429. # an array containing, 'non-tag', 'tag', 'non-tag', 'tag'
  430. # so if we alternate this variable, we know
  431. # what to look for. This is alternate to
  432. # line.startswith("{{")
  433. in_tag = False
  434. extend = None
  435. pre_extend = True
  436. # Use a list to store everything in
  437. # This is because later the code will "look ahead"
  438. # for missing strings or brackets.
  439. ij = self.r_tag.split(text)
  440. # j = current index
  441. # i = current item
  442. stack = self.stack
  443. for j in range(len(ij)):
  444. i = ij[j]
  445. if i:
  446. if not stack:
  447. self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
  448. # Our current element in the stack.
  449. top = stack[-1]
  450. if in_tag:
  451. line = i
  452. # Get rid of delimiters
  453. line = line[len(self.delimiters[0]):-len(self.delimiters[1])].strip()
  454. # This is bad juju, but let's do it anyway
  455. if not line:
  456. continue
  457. # We do not want to replace the newlines in code,
  458. # only in block comments.
  459. def remove_newline(re_val):
  460. # Take the entire match and replace newlines with
  461. # escaped newlines.
  462. return re_val.group(0).replace('\n', '\\n')
  463. # Perform block comment escaping.
  464. # This performs escaping ON anything
  465. # in between """ and """
  466. line = sub(TemplateParser.r_multiline,
  467. remove_newline,
  468. line)
  469. if line.startswith('='):
  470. # IE: {{=response.title}}
  471. name, value = '=', line[1:].strip()
  472. else:
  473. v = line.split(' ', 1)
  474. if len(v) == 1:
  475. # Example
  476. # {{ include }}
  477. # {{ end }}
  478. name = v[0]
  479. value = ''
  480. else:
  481. # Example
  482. # {{ block pie }}
  483. # {{ include "layout.html" }}
  484. # {{ for i in range(10): }}
  485. name = v[0]
  486. value = v[1]
  487. # This will replace newlines in block comments
  488. # with the newline character. This is so that they
  489. # retain their formatting, but squish down to one
  490. # line in the rendered template.
  491. # First check if we have any custom lexers
  492. if name in self.lexers:
  493. # Pass the information to the lexer
  494. # and allow it to inject in the environment
  495. # You can define custom names such as
  496. # '{{<<variable}}' which could potentially
  497. # write unescaped version of the variable.
  498. self.lexers[name](parser=self,
  499. value=value,
  500. top=top,
  501. stack=stack)
  502. elif name == '=':
  503. # So we have a variable to insert into
  504. # the template
  505. buf = "\n%s(%s)" % (self.writer, value)
  506. top.append(Node(buf, pre_extend=pre_extend))
  507. elif name == 'block' and not value.startswith('='):
  508. # Make a new node with name.
  509. node = BlockNode(name=value.strip(),
  510. pre_extend=pre_extend,
  511. delimiters=self.delimiters)
  512. # Append this node to our active node
  513. top.append(node)
  514. # Make sure to add the node to the stack.
  515. # so anything after this gets added
  516. # to this node. This allows us to
  517. # "nest" nodes.
  518. stack.append(node)
  519. elif name == 'end' and not value.startswith('='):
  520. # We are done with this node.
  521. # Save an instance of it
  522. self.blocks[top.name] = top
  523. # Pop it.
  524. stack.pop()
  525. elif name == 'super' and not value.startswith('='):
  526. # Get our correct target name
  527. # If they just called {{super}} without a name
  528. # attempt to assume the top blocks name.
  529. if value:
  530. target_node = value
  531. else:
  532. target_node = top.name
  533. # Create a SuperNode instance
  534. node = SuperNode(name=target_node,
  535. pre_extend=pre_extend)
  536. # Add this to our list to be taken care of
  537. self.super_nodes.append(node)
  538. # And put in in the tree
  539. top.append(node)
  540. elif name == 'include' and not value.startswith('='):
  541. # If we know the target file to include
  542. if value:
  543. self.include(top, value)
  544. # Otherwise, make a temporary include node
  545. # That the child node will know to hook into.
  546. else:
  547. include_node = BlockNode(
  548. name='__include__' + self.name,
  549. pre_extend=pre_extend,
  550. delimiters=self.delimiters)
  551. top.append(include_node)
  552. elif name == 'extend' and not value.startswith('='):
  553. # We need to extend the following
  554. # template.
  555. extend = value
  556. pre_extend = False
  557. else:
  558. # If we don't know where it belongs
  559. # we just add it anyways without formatting.
  560. if line and in_tag:
  561. # Split on the newlines >.<
  562. tokens = line.split('\n')
  563. # We need to look for any instances of
  564. # for i in range(10):
  565. # = i
  566. # pass
  567. # So we can properly put a response.write() in place.
  568. continuation = False
  569. len_parsed = 0
  570. for k, token in enumerate(tokens):
  571. token = tokens[k] = token.strip()
  572. len_parsed += len(token)
  573. if token.startswith('='):
  574. if token.endswith('\\'):
  575. continuation = True
  576. tokens[k] = "\n%s(%s" % (
  577. self.writer, token[1:].strip())
  578. else:
  579. tokens[k] = "\n%s(%s)" % (
  580. self.writer, token[1:].strip())
  581. elif continuation:
  582. tokens[k] += ')'
  583. continuation = False
  584. buf = "\n%s" % '\n'.join(tokens)
  585. top.append(Node(buf, pre_extend=pre_extend))
  586. else:
  587. # It is HTML so just include it.
  588. buf = "\n%s(%r, escape=False)" % (self.writer, i)
  589. top.append(Node(buf, pre_extend=pre_extend))
  590. # Remember: tag, not tag, tag, not tag
  591. in_tag = not in_tag
  592. # Make a list of items to remove from child
  593. to_rm = []
  594. # Go through each of the children nodes
  595. for node in self.child_super_nodes:
  596. # If we declared a block that this node wants to include
  597. if node.name in self.blocks:
  598. # Go ahead and include it!
  599. node.value = self.blocks[node.name]
  600. # Since we processed this child, we don't need to
  601. # pass it along to the parent
  602. to_rm.append(node)
  603. # Remove some of the processed nodes
  604. for node in to_rm:
  605. # Since this is a pointer, it works beautifully.
  606. # Sometimes I miss C-Style pointers... I want my asterisk...
  607. self.child_super_nodes.remove(node)
  608. # If we need to extend a template.
  609. if extend:
  610. self.extend(extend)
  611. # We need this for integration with gluon
  612. def parse_template(filename,
  613. path='views/',
  614. context=dict(),
  615. lexers={},
  616. delimiters=('{{', '}}')
  617. ):
  618. """
  619. filename can be a view filename in the views folder or an input stream
  620. path is the path of a views folder
  621. context is a dictionary of symbols used to render the template
  622. """
  623. # First, if we have a str try to open the file
  624. if isinstance(filename, str):
  625. try:
  626. fp = open(os.path.join(path, filename), 'rb')
  627. text = fp.read()
  628. fp.close()
  629. except IOError:
  630. raise RestrictedError(filename, '', 'Unable to find the file')
  631. else:
  632. text = filename.read()
  633. # Use the file contents to get a parsed template and return it.
  634. return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
  635. def get_parsed(text):
  636. """
  637. Returns the indented python code of text. Useful for unit testing.
  638. """
  639. return str(TemplateParser(text))
  640. class DummyResponse():
  641. def __init__(self):
  642. self.body = StringIO.StringIO()
  643. def write(self, data, escape=True):
  644. if not escape:
  645. self.body.write(str(data))
  646. elif hasattr(data, 'xml') and callable(data.xml):
  647. self.body.write(data.xml())
  648. else:
  649. # make it a string
  650. if not isinstance(data, (str, unicode)):
  651. data = str(data)
  652. elif isinstance(data, unicode):
  653. data = data.encode('utf8', 'xmlcharrefreplace')
  654. data = cgi.escape(data, True).replace("'", "&#x27;")
  655. self.body.write(data)
  656. class NOESCAPE():
  657. """
  658. A little helper to avoid escaping.
  659. """
  660. def __init__(self, text):
  661. self.text = text
  662. def xml(self):
  663. return self.text
  664. # And this is a generic render function.
  665. # Here for integration with gluon.
  666. def render(content="hello world",
  667. stream=None,
  668. filename=None,
  669. path=None,
  670. context={},
  671. lexers={},
  672. delimiters=('{{', '}}'),
  673. writer='response.write'
  674. ):
  675. """
  676. >>> render()
  677. 'hello world'
  678. >>> render(content='abc')
  679. 'abc'
  680. >>> render(content='abc\\'')
  681. "abc'"
  682. >>> render(content='a"\\'bc')
  683. 'a"\\'bc'
  684. >>> render(content='a\\nbc')
  685. 'a\\nbc'
  686. >>> render(content='a"bcd"e')
  687. 'a"bcd"e'
  688. >>> render(content="'''a\\nc'''")
  689. "'''a\\nc'''"
  690. >>> render(content="'''a\\'c'''")
  691. "'''a\'c'''"
  692. >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
  693. '0<br />1<br />2<br />3<br />4<br />'
  694. >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
  695. '0<br />1<br />2<br />3<br />4<br />'
  696. >>> render(content="{{='''hello\\nworld'''}}")
  697. 'hello\\nworld'
  698. >>> render(content='{{for i in range(3):\\n=i\\npass}}')
  699. '012'
  700. """
  701. # here to avoid circular Imports
  702. try:
  703. from globals import Response
  704. except ImportError:
  705. # Working standalone. Build a mock Response object.
  706. Response = DummyResponse
  707. # Add it to the context so we can use it.
  708. if not 'NOESCAPE' in context:
  709. context['NOESCAPE'] = NOESCAPE
  710. # save current response class
  711. if context and 'response' in context:
  712. old_response_body = context['response'].body
  713. context['response'].body = StringIO.StringIO()
  714. else:
  715. old_response_body = None
  716. context['response'] = Response()
  717. # If we don't have anything to render, why bother?
  718. if not content and not stream and not filename:
  719. raise SyntaxError("Must specify a stream or filename or content")
  720. # Here for legacy purposes, probably can be reduced to
  721. # something more simple.
  722. close_stream = False
  723. if not stream:
  724. if filename:
  725. stream = open(filename, 'rb')
  726. close_stream = True
  727. elif content:
  728. stream = StringIO.StringIO(content)
  729. # Execute the template.
  730. code = str(TemplateParser(stream.read(
  731. ), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer))
  732. try:
  733. exec(code) in context
  734. except Exception:
  735. # for i,line in enumerate(code.split('\n')): print i,line
  736. raise
  737. if close_stream:
  738. stream.close()
  739. # Returned the rendered content.
  740. text = context['response'].body.getvalue()
  741. if old_response_body is not None:
  742. context['response'].body = old_response_body
  743. return text
  744. if __name__ == '__main__':
  745. import doctest
  746. doctest.testmod()