#!/usr/bin/env python """Yet another templating module with support for embedded Python code. See the end of the module source code and the _test() function for example usage. Usage as a script: PyEmb.py infile[.pye] [outfile] Processes infile and writes output to stdout or outfile if given. Usage as a CGI script: See documentation for the _do_cgi() function. """ import os, re, sys import StringIO __all__ = ['Error', 'ParseError', 'EmbeddedScript', 'EmbeddedPython', 'SpecialHTML'] __author__ = "Christopher Arndt <chris.arndt@web.de>" __version__ = "1.1" PRINTECHO = 1 class Error(Exception): pass class ParseError(Error): pass def include(fn): try: f = open(fn) s = f.read() f.close() except: return '' print s class EmbeddedScript: language = None start_rx = re.compile( r"""<script\b.*?(language=["']?(?P<lang>\w*)\b.*?)?>""", re.I | re.S) end_rx = re.compile(r'</script.*?>', re.I | re.S) environment = {} def __init__(self, template=None): if template: self.set_template(template) else: self.source = "" def set_template(self, template): self.source = template def _parse(self, source): i = 0 script_areas = [] #print source while 1: start_m = self.start_rx.search(source, i) if start_m: if self.language: language = start_m.groupdict().get("lang") end_m = self.end_rx.search(source, start_m.end()) if end_m: if self.language: if not (language and language.lower() \ == self.language): break script = source[start_m.end():end_m.start()] span = start_m.start(), end_m.end() script_areas.append((script, span)) i = end_m.end() else: raise ParseError, "Missing script end tag." else: break return script_areas def _subst(self, source=None): if source is None: source = self.source script_areas = self._parse(source) env = self._mk_env() while script_areas: #print script_areas i = 0 output = [] for t, span in script_areas: output.append(source[i:span[0]]) output.append(self._process(t, env)) i = span[1] output.append(source[i:]) source = "".join(output) script_areas = self._parse(source) return source def _mk_env(self): return self.environment.copy() def _process(self, s, env): return s def __str__(self): return self._subst().lstrip() def write(self, file=None): if type(file) == type(sys.stdout): f = file elif type(file) == type(""): s = str(self) if os.path.exists(file): if compare_s2f(s, file): f = open(file, 'w') f.write(s) f.close() if PRINTECHO: print "wrote: '%s'" % file else: if PRINTECHO: print "file unchanged: '%s" % file else: f = open(file, 'w') f.write(s) f.close() if PRINTECHO: print "wrote: '%s'" % file return else: f = sys.stdout f.write(str(self)) class EmbeddedPython(EmbeddedScript): language = "python" def _mk_env(self): env = self.environment.copy() env['file'] = self.filename env['include'] = include return env def set_template(self, template): f = open(template) self.source = f.read() f.close() self.filename = template self.parsed = 0 def _process(self, s, env): save_stdout = sys.stdout sb = StringIO.StringIO() sys.stdout = sb try: sc = self._strip_left(s) if sc and sc[0] == "!": out = str(eval(sc[1:], env)) else: c = compile(sc, getattr(self, 'filename', '<string>'), 'exec') exec c in env sb.seek(0) out = sb.read().rstrip() except: import traceback out = "<pre>\n" out += "".join(apply(traceback.format_exception, sys.exc_info())) out += "</pre>\n" sys.stdout = save_stdout sb.close() return out def _strip_left(self, s): lines = s.splitlines(1) edge_width = 0 for i in xrange(len(lines)): if lines[i].strip(): edge_width = len(lines[i]) - len(lines[i].lstrip()) break l = [] for line in lines[i:]: line = line[edge_width:].rstrip() if line: l.append(line + '\n') return ''.join(l) # an example of how easy you can change the tags that enclose the script area class SpecialHTML(EmbeddedPython): language = None start_rx = re.compile(r"<\[") end_rx = re.compile(r"\]>") # shamelessly ripped from HTMLgen def compare_s2f(s, f2): """Helper to compare a string to a file, return 0 if they are equal.""" BUFSIZE = 8192 i = 0 fp2 = open(f2) try: while 1: try: b1 = s[i: i + BUFSIZE] i = i + BUFSIZE except IndexError: b1 = '' b2 = fp2.read(BUFSIZE) if not b1 and not b2: return 0 c = cmp(b1, b2) if c: return c finally: fp2.close() def _do_cgi(): """CGI support. Put out HTTP header and processed document to stdout. The name of the template file is taken from the environment variable PATH_TRANSLATED, which should contain the real path of of the template file on the server file system. Here's how to set up Apache, so that you can directly serve template files (with the extension .pye) and process them on-the-fly: - Add the following to your 'httpd.conf' or '.htaccess' file: AddHandler embedded-python .pye Action embedded-python /cgi-bin/PyEmb.py AddType text/x-pye .pye - Install this module in your cgi-bin directory and make it executable. - Restart Apache if necessary. """ try: import cgitb; cgitb.enable() except: pass print "Content-type: text/html\n" if os.environ.has_key("PATH_TRANSLATED"): d = SpecialHTML(os.environ["PATH_TRANSLATED"]) d.write() else: import cgi print "<pre>\n" + cgi.escape(_test()) + "</pre>" def _test(): """Module test function.""" d = SpecialHTML() d.source = """<[ title = "PyEmb example" import time date = time.asctime(time.localtime(time.time())) ]> <html> <head> <title><[!title]></title> <head> A simple equation: <blockquote> 2 + 2 = <[!2 + 2]> </blockquote> <body> This document was generated by PyEmb on: <[!date]> <body> """ s = "This is the input document:\n" s += "===========================\n" s += d.source s += "\nThis is the output:\n" s += "===================\n" s += str(d) return s if __name__ == '__main__': if os.environ.has_key('REQUEST_METHOD'): _do_cgi() elif len(sys.argv) > 1: d = SpecialHTML(sys.argv.pop(1)) if len(sys.argv) > 1: d.write(sys.argv.pop(1)) else: d.write() else: print _test()