Hi,I've been using the web2py template engine for a while. I don't remember if I had to make some change to the template.py module nor the version of the web2py this module comes from so I attach it in this email. This was the way I could make it work (there is probably a simpler one):
from template import TemplateParser context = {} output = cStringIO.StringIO() def response_writer(data, escape=False): output.write(unicode(data)) context['response_writer'] = response_writer source = self._template() exec(str(TemplateParser(source, context=context, writer='response_writer'))) in context content = output.getvalue()I have a question about this module. It's GPL3 license. If I modify it an ditribute it in an application, do this application have to be GPL3 licensed?
I apologize if my english if very bad. My language is spanish. El 06/06/11 00:59, Ryan Seto escribió:
Thanks! That does solve the import restricted dependency. The import globals for the Response() object is still an issue. I tried fiddling with my copy to build a mock Response() object if we can't import globals. This is what I have so far: gluon/template.py | line 867 ~~~~~ # Here to avoid circular Imports try: from globals import Response except: import cStringIO from xml.sax.saxutils import escape, quoteattr class Response(): def __init__(self): self.body = cStringIO.StringIO() def write(self, data, escape=True): if not escape: self.body.write(str(data)) elif hasattr(data,'xml') and callable(data.xml): self.body.write(data.xml()) else: # otherwise, make it a string if not isinstance(data, (str, unicode)): data = str(data) elif isinstance(data, unicode): data = data.encode('utf8', 'xmlcharrefreplace') self.body.write(data) ~~~~~ I was planning to escape the data with the escape and quoteattr provided from xml.sax.saxutils, but I wasn't successful with that, so I left it out for now. Here's my code snippet: nixie/util/text.py | line 19 ~~~~~ import os, subprocess, paths, template def render(inFile): content = pandoc(str(inFile)) templateFile = os.path.join(paths.get_prog_root(), 'templates', 'view.html') styles = [] styles.append(os.path.join(paths.get_prog_root(), 'css', 'style.css')) return template.render( filename=templateFile, context=dict(content=content, styles=styles) ) ~~~~~ templates/view.html ~~~~~ <html> <head> {{for css in styles:}} <link rel="stylesheet" href="{{=css}}" type="text/css" /> {{pass}} </head> <body> {{=content}} </body> </html> ~~~~~ When I run this, I get an error message that doesn't really help me much. Here's the output: ~~~~~ C:\projects\nixie>c:\Python26\python.exe Nixie.py README.txt Traceback (most recent call last): File "C:\projects\nixie\nixie\qt\NixieAccessManager.py", line 41, in createRequest reply = NixieReply(request.url(), self.GetOperation, parent=self) File "C:\projects\nixie\nixie\qt\NixieReply.py", line 30, in __init__ self.content = text.render(url.toLocalFile()) File "C:\projects\nixie\nixie\util\text.py", line 22, in render content = pandoc(str(inFile)) File "C:\projects\nixie\nixie\util\text.py", line 63, in pandoc cwd = cwd File "c:\Python26\lib\subprocess.py", line 623, in __init__ errread, errwrite) File "c:\Python26\lib\subprocess.py", line 833, in _execute_child startupinfo) WindowsError: [Error 123] The filename, directory name, or volume label syntax is incorrect ~~~~~ Although it looks like pandoc(str(inFile)) might be the culprit from the stack trace, if I just use the output from pandoc(str(inFile)), everything works fine, so I doubt that this is the cause. I really appreciate your help. I've started trying Pandoc (http://johnmacfarlane.net/pandoc/) instead of the python-markdown module, and I noticed that Pandoc comes with it's own template system. So, it may make more sense for me to use Pandoc's templates instead, if I decide to go with it. On Sun, Jun 5, 2011 at 10:45 PM, Massimo Di Pierro <massimo.dipie...@gmail.com> wrote:check trunk. I removed it. I am sure we can do better. On Jun 5, 2011, at 9:26 PM, Ryan Seto wrote:Thank you very much for your prompt response. It looks like the file gluon/template.py does pull in some extra dependencies, however. It tries to import restricted on line 20 and import globals on line 863. The restricted module dependency may be easy to remove, since it appears that it only uses it for raising exceptions. However, it looks like the Response object is used from the globals module. On Sun, Jun 5, 2011 at 9:12 PM, Massimo Di Pierro <massimo.dipie...@gmail.com> wrote:On Jun 4, 7:58 pm, Ryan Seto<mr.werew...@gmail.com> wrote:I really like how elegant and simple it is to create views in web2py. Would it be possible to use the view/template engine in a standalone application?yes. you only need the file gluon/template.py look at the example inside. You only the render function.I'm writing a desktop application to view formatted text, like markdown, using PyQT's QtWebKit to render the generated html, and I would like to integrate web2py's method for generating views into my project. I've been looking through web2py's source and the mailing list, and it seems that response.render( view_text, dict() ) might be the closest thing to what I'm looking for. However, it looks like there's a lot of dependencies wrapped around it and the objects weren't made to be used in the context of another application. If this is the case, would it make sense to compartmentalize the parts for rendering a view into it's own module so they can be used in a standalone application, similar to the dal? I would be willing to come up with a patch for this, if I could get some hints on where to start.
#!/usr/bin/env python # -*- coding: utf-8 -*- """ This file is part of the web2py Web Framework (Copyrighted, 2007-2011). License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) Author: Thadeus Burgess Contributors: - Thank you to Massimo Di Pierro for creating the original gluon/template.py - Thank you to Jonathan Lundell for extensively testing the regex on Jython. - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. """ import os import re import cStringIO #import restricted class Node(object): """ Basic Container Object """ def __init__(self, value = None, pre_extend = False): self.value = value self.pre_extend = pre_extend def __str__(self): return str(self.value) class SuperNode(Node): def __init__(self, name = '', pre_extend = False): self.name = name self.value = None self.pre_extend = pre_extend def __str__(self): if self.value: return str(self.value) else: 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." ) def __repr__(self): return "%s->%s" % (self.name, self.value) class BlockNode(Node): """ Block Container. This Node can contain other Nodes and will render in a heirarchical order of when nodes were added. ie:: {{ block test }} This is default block test {{ end }} """ def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')): """ name - Name of this Node. """ self.nodes = [] self.name = name self.pre_extend = pre_extend self.left, self.right = delimiters def __repr__(self): lines = ['%sblock %s%s' % (self.left,self.name,self.right)] for node in self.nodes: lines.append(str(node)) lines.append('%send%s' % (self.left, self.right)) return ''.join(lines) def __str__(self): """ Get this BlockNodes content, not including children Nodes """ lines = [] for node in self.nodes: if not isinstance(node, BlockNode): lines.append(str(node)) return ''.join(lines) def append(self, node): """ Add an element to the nodes. Keyword Arguments - node -- Node object or string to append. """ if isinstance(node, str) or isinstance(node, Node): self.nodes.append(node) else: raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node) def extend(self, other): """ Extend the list of nodes with another BlockNode class. Keyword Arugments - other -- BlockNode or Content object to extend from. """ if isinstance(other, BlockNode): self.nodes.extend(other.nodes) else: raise TypeError("Invalid type, must be instance of ``BlockNode``. %s" % other) def output(self, blocks): """ Merges all nodes into a single string. blocks -- Dictionary of blocks that are extending from this template. """ lines = [] # Get each of our nodes for node in self.nodes: # If we have a block level node. if isinstance(node, BlockNode): # If we can override this block. if node.name in blocks: # Override block from vars. lines.append(blocks[node.name].output(blocks)) # Else we take the default else: lines.append(node.output(blocks)) # Else its just a string else: lines.append(str(node)) # Now combine all of our lines together. return ''.join(lines) class Content(BlockNode): """ Parent Container -- Used as the root level BlockNode. Contains functions that operate as such. """ def __init__(self, name = "ContentBlock", pre_extend = False): """ Keyword Arugments name -- Unique name for this BlockNode """ self.name = name self.nodes = [] self.blocks = {} self.pre_extend = pre_extend def __str__(self): lines = [] # For each of our nodes for node in self.nodes: # If it is a block node. if isinstance(node, BlockNode): # And the node has a name that corresponds with a block in us if node.name in self.blocks: # Use the overriding output. lines.append(self.blocks[node.name].output(self.blocks)) else: # Otherwise we just use the nodes output. lines.append(node.output(self.blocks)) else: # It is just a string, so include it. lines.append(str(node)) # Merge our list together. return ''.join(lines) def _insert(self, other, index = 0): """ Inserts object at index. """ if isinstance(other, str) or isinstance(other, Node): self.nodes.insert(index, other) else: raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.") def insert(self, other, index = 0): """ Inserts object at index. You may pass a list of objects and have them inserted. """ if isinstance(other, (list, tuple)): # Must reverse so the order stays the same. other.reverse() for item in other: self._insert(item, index) else: self._insert(other, index) def append(self, node): """ Adds a node to list. If it is a BlockNode then we assign a block for it. """ if isinstance(node, str) or isinstance(node, Node): self.nodes.append(node) if isinstance(node, BlockNode): self.blocks[node.name] = node else: raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node) def extend(self, other): """ Extends the objects list of nodes with another objects nodes """ if isinstance(other, BlockNode): self.nodes.extend(other.nodes) self.blocks.update(other.blocks) else: raise TypeError("Invalid type, must be instance of ``BlockNode``. %s" % node) def clear_content(self): self.nodes = [] class TemplateParser(object): r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL) r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL) # These are used for re-indentation. # Indent + 1 re_block = re.compile('^(elif |else:|except:|except |finally:).*$', re.DOTALL) # Indent - 1 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) # Indent - 1 re_pass = re.compile('^pass( .*)?$', re.DOTALL) def __init__(self, text, name = "ParserContainer", context = dict(), path = 'views/', writer = 'response.write', lexers = {}, delimiters = ('{{','}}'), _super_nodes = [], ): """ text -- text to parse context -- context to parse in path -- folder path to temlpates writer -- string of writer class to use lexers -- dict of custom lexers to use. delimiters -- for example ('{{','}}') _super_nodes -- a list of nodes to check for inclusion this should only be set by "self.extend" It contains a list of SuperNodes from a child template that need to be handled. """ # Keep a root level name. self.name = name # Raw text to start parsing. self.text = text # Writer to use. (refer to the default for an example.) # This will end up as # "%s(%s, escape=False)" % (self.writer, value) self.writer = writer # Dictionary of custom name lexers to use. if isinstance(lexers, dict): self.lexers = lexers else: self.lexers = {} # Path of templates self.path = path # Context for templates. self.context = context # allow optional alterantive delimiters self.delimiters = delimiters if delimiters!=('{{','}}'): escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) # Create a root level Content that everything will go into. self.content = Content(name=name) # Stack will hold our current stack of nodes. # As we descend into a node, it will be added to the stack # And when we leave, it will be removed from the stack. # self.content should stay on the stack at all times. self.stack = [self.content] # This variable will hold a reference to every super block # that we come across in this template. self.super_nodes = [] # This variable will hold a reference to the child # super nodes that need handling. self.child_super_nodes = _super_nodes # This variable will hold a reference to every block # that we come across in this template self.blocks = {} # Begin parsing. self.parse(text) def to_string(self): """ Returns the parsed template with correct indentation. Used to make it easier to port to python3. """ return self.reindent(str(self.content)) def __str__(self): "Make sure str works exactly the same as python 3" return self.to_string() def __unicode__(self): "Make sure str works exactly the same as python 3" return self.to_string() def reindent(self, text): """ Reindents a string of unindented python code. """ # Get each of our lines into an array. lines = text.split('\n') # Our new lines new_lines = [] # Keeps track of how many indents we have. # Used for when we need to drop a level of indentaiton # only to re-indent on the next line. credit = 0 # Current indentation k = 0 ################# # THINGS TO KNOW ################# # k += 1 means indent # k -= 1 means unindent # credit = 1 means unindent on the next line. for raw_line in lines: line = raw_line.strip() # ignore empty lines if not line: continue # If we have a line that contains python code that # should be un-indented for this line of code. # and then re-indented for the next line. if TemplateParser.re_block.match(line): k = k + credit - 1 # We obviously can't have a negative indentation k = max(k,0) # Add the indentation! new_lines.append(' '*(4*k)+line) # Bank account back to 0 again :( credit = 0 # If we are a pass block, we obviously de-dent. if TemplateParser.re_pass.match(line): k -= 1 # If we are any of the following, de-dent. # However, we should stay on the same level # But the line right after us will be de-dented. # So we add one credit to keep us at the level # While moving back one indentation level. if TemplateParser.re_unblock.match(line): credit = 1 k -= 1 # If we are an if statement, a try, or a semi-colon we # probably need to indent the next line. if line.endswith(':') and not line.startswith('#'): k += 1 # This must come before so that we can raise an error with the # right content. new_text = '\n'.join(new_lines) if k > 0: self._raise_error('missing "pass" in view', new_text) elif k < 0: self._raise_error('too many "pass" in view', new_text) return new_text def _raise_error(self, message='', text=None): """ Raises an error using itself as the filename and textual content. """ if text: raise restricted.RestrictedError(self.name, text, message) else: raise restricted.RestrictedError(self.name, self.text, message) def _get_file_text(self, filename): """ Attempt to open ``filename`` and retrieve its text. This will use self.path to search for the file. """ # If they didn't specify a filename, how can we find one! if not filename.strip(): self._raise_error('Invalid template filename') # Get the file name, filename looks like ``"template.html"``. # We need to eval to remove the qoutations and get the string type. filename = eval(filename, self.context) # Get the path of the file on the system. filepath = os.path.join(self.path, filename) # Lets try to read teh text. try: fileobj = open(filepath, 'rb') text = fileobj.read() fileobj.close() except IOError: self._raise_error('Unable to open included view file: ' + filepath) return text def include(self, content, filename): """ Include ``filename`` here. """ text = self._get_file_text(filename) t = TemplateParser(text, name = filename, context = self.context, path = self.path, writer = self.writer, delimiters = self.delimiters) content.append(t.content) def extend(self, filename): """ Extend ``filename``. Anything not declared in a block defined by the parent will be placed in the parent templates ``{{include}}`` block. """ text = self._get_file_text(filename) # Create out nodes list to send to the parent super_nodes = [] # We want to include any non-handled nodes. super_nodes.extend(self.child_super_nodes) # And our nodes as well. super_nodes.extend(self.super_nodes) t = TemplateParser(text, name = filename, context = self.context, path = self.path, writer = self.writer, delimiters = self.delimiters, _super_nodes = super_nodes) # Make a temporary buffer that is unique for parent # template. buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) pre = [] # Iterate through each of our nodes for node in self.content.nodes: # If a node is a block if isinstance(node, BlockNode): # That happens to be in the parent template if node.name in t.content.blocks: # Do not include it continue if isinstance(node, Node): # Or if the node was before the extension # we should not include it if node.pre_extend: pre.append(node) continue # Otherwise, it should go int the # Parent templates {{include}} section. buf.append(node) else: buf.append(node) # Clear our current nodes. We will be replacing this with # the parent nodes. self.content.nodes = [] # Set our include, unique by filename t.content.blocks['__include__' + filename] = buf # Make sure our pre_extended nodes go first t.content.insert(pre) # Then we extend our blocks t.content.extend(self.content) # Work off the parent node. self.content = t.content def parse(self, text): # Basically, r_tag.split will split the text into # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' # so if we alternate this variable, we know # what to look for. This is alternate to # line.startswith("{{") in_tag = False extend = None pre_extend = True # Use a list to store everything in # This is because later the code will "look ahead" # for missing strings or brackets. ij = self.r_tag.split(text) # j = current index # i = current item for j in range(len(ij)): i = ij[j] if i: if len(self.stack) == 0: self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') # Our current element in the stack. top = self.stack[-1] if in_tag: line = i # If we are missing any strings!!!! # This usually happens with the following example # template code # # {{a = '}}'}} # or # {{a = '}}blahblah{{'}} # # This will fix these # This is commented out because the current template # system has this same limitation. Since this has a # performance hit on larger templates, I do not recommend # using this code on production systems. This is still here # for "i told you it *can* be fixed" purposes. # # # if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: # # # Look ahead # la = 1 # nextline = ij[j+la] # # # As long as we have not found our ending # # brackets keep going # while '}}' not in nextline: # la += 1 # nextline += ij[j+la] # # clear this line, so we # # don't attempt to parse it # # this is why there is an "if i" # # around line 530 # ij[j+la] = '' # # # retrieve our index. # index = nextline.index('}}') # # # Everything before the new brackets # before = nextline[:index+2] # # # Everything after # after = nextline[index+2:] # # # Make the next line everything after # # so it parses correctly, this *should* be # # all html # ij[j+1] = after # # # Add everything before to the current line # line += before # Get rid of '{{' and '}}' line = line[2:-2].strip() # This is bad joo joo, but lets do it anyway if not line: continue # We do not want to replace the newlines in code, # only in block comments. def remove_newline(re_val): # Take the entire match and replace newlines with # escaped newlines. return re_val.group(0).replace('\n', '\\n') # Perform block comment escaping. # This performs escaping ON anything # in between """ and """ line = re.sub(TemplateParser.r_multiline, remove_newline, line) if line.startswith('='): # IE: {{=response.title}} name, value = '=', line[1:].strip() else: v = line.split(' ', 1) if len(v) == 1: # Example # {{ include }} # {{ end }} name = v[0] value = '' else: # Example # {{ block pie }} # {{ include "layout.html" }} # {{ for i in range(10): }} name = v[0] value = v[1] # This will replace newlines in block comments # with the newline character. This is so that they # retain their formatting, but squish down to one # line in the rendered template. # First lets check if we have any custom lexers if name in self.lexers: # Pass the information to the lexer # and allow it to inject in the environment # You can define custom names such as # '{{<<variable}}' which could potentially # write unescaped version of the variable. self.lexers[name](parser = self, value = value, top = top, stack = stack,) elif name == '=': # So we have a variable to insert into # the template buf = "\n%s(%s)" % (self.writer, value) top.append(Node(buf, pre_extend = pre_extend)) elif name == 'block': # Make a new node with name. node = BlockNode(name = value.strip(), pre_extend = pre_extend, delimiters = self.delimiters) # Append this node to our active node top.append(node) # Make sure to add the node to the stack. # so anything after this gets added # to this node. This allows us to # "nest" nodes. self.stack.append(node) elif name == 'end': # We are done with this node. # Save an instance of it self.blocks[top.name] = top # Pop it. self.stack.pop() elif name == 'super': # Lets get our correct target name # If they just called {{super}} without a name # attempt to assume the top blocks name. if value: target_node = value else: target_node = top.name # Create a SuperNode instance node = SuperNode(name = target_node, pre_extend = pre_extend) # Add this to our list to be taken care of self.super_nodes.append(node) # And put in in the tree top.append(node) elif name == 'include': # If we know the target file to include if value: self.include(top, value) # Otherwise, make a temporary include node # That the child node will know to hook into. else: include_node = BlockNode(name = '__include__' + self.name, pre_extend = pre_extend, delimiters = self.delimiters) top.append(include_node) elif name == 'extend': # We need to extend the following # template. extend = value pre_extend = False else: # If we don't know where it belongs # we just add it anyways without formatting. if line and in_tag: # Lets go ahead and split on the newlines >.< tokens = line.split('\n') # We need to look for any instances of # for i in range(10): # = i # pass # So we can properly put a response.write() in place. continuation = False len_parsed = 0 for k in range(len(tokens)): tokens[k] = tokens[k].strip() len_parsed += len(tokens[k]) if tokens[k].startswith('='): if tokens[k].endswith('\\'): continuation = True tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) else: tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) elif continuation: tokens[k] += ')' continuation = False buf = "\n%s" % '\n'.join(tokens) top.append(Node(buf, pre_extend = pre_extend)) else: # It is HTML so just include it. buf = "\n%s(%r, escape=False)" % (self.writer, i) top.append(Node(buf, pre_extend = pre_extend)) # Remeber, tag, not tag, tag, not tag in_tag = not in_tag # Lets make a list of items to remove from child to_rm = [] # Go through each of the children nodes for node in self.child_super_nodes: # If we declared a block that this node wants to include if node.name in self.blocks: # Lets go ahead and include it! node.value = self.blocks[node.name] # Since we processed this child, we don't need to # pass it along to the parent to_rm.append(node) # So now lets remove some of the processed nodes for node in to_rm: # Since this is a pointer, it works beautifully. # Sometimes I miss C-Style pointers... I want my asterisk... self.child_super_nodes.remove(node) # If we need to extend a template. if extend: self.extend(extend) # We need this for integration with gluon def parse_template(filename, path = 'views/', context = dict(), lexers = {}, delimiters = ('{{','}}') ): """ filename can be a view filename in the views folder or an input stream path is the path of a views folder context is a dictionary of symbols used to render the template """ # First, if we have a str try to open the file if isinstance(filename, str): try: fp = open(os.path.join(path, filename), 'rb') text = fp.read() fp.close() except IOError: raise restricted.RestrictedError(filename, '', 'Unable to find the file') else: text = filename.read() # Use the file contents to get a parsed template and return it. return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters)) def get_parsed(text): """ Returns the indented python code of text. Useful for unit testing. """ return str(TemplateParser(text)) # And this is a generic render function. # Here for integration with gluon. def render(content = "hello world", stream = None, filename = None, path = None, context = {}, lexers = {}, delimiters = ('{{','}}') ): """ >>> render() 'hello world' >>> render(content='abc') 'abc' >>> render(content='abc\\'') "abc'" >>> render(content='a"\\'bc') 'a"\\'bc' >>> render(content='a\\nbc') 'a\\nbc' >>> render(content='a"bcd"e') 'a"bcd"e' >>> render(content="'''a\\nc'''") "'''a\\nc'''" >>> render(content="'''a\\'c'''") "'''a\'c'''" >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) '0<br />1<br />2<br />3<br />4<br />' >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) '0<br />1<br />2<br />3<br />4<br />' >>> render(content="{{='''hello\\nworld'''}}") 'hello\\nworld' >>> render(content='{{for i in range(3):\\n=i\\npass}}') '012' """ # Here to avoid circular Imports import globals # If we don't have anything to render, why bother? if not content and not stream and not filename: raise SyntaxError, "Must specify a stream or filename or content" # Here for legacy purposes, probably can be reduced to something more simple. close_stream = False if not stream: if filename: stream = open(filename, 'rb') close_stream = True elif content: stream = cStringIO.StringIO(content) # Get a response class. context['response'] = globals.Response() # Execute the template. exec(str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters))) in context if close_stream: stream.close() # Returned the rendered content. return context['response'].body.getvalue() if __name__ == '__main__': import doctest doctest.testmod()
<<attachment: kverdecia.vcf>>