""" * ==================================================================== * Copyright (c) 2001 Jon Travis. All rights reserved. * See the file COPYING for information on usage and redistribution. * ==================================================================== emb_html: A processor for Python embedded within HTML This module allows Python embedded within HTML documents to be processed. Usage: The primary interface to this module comes through the TextProcessor object. This object can register processors to handle specific ways of evaluating Python code, such as the LoadTimeProcessor, ExecProcessor, or EvalProcessor. A typical session might look like: processor = TextProcessor() processor.register_processor(EvalProcessor) processor.register_processor(ExecProcessor) global_namespace = {} processor.gobble(global_namespace, data, 0, len(data)) # Process cached processor data for each request # ... processor.process(global_namespace, output_file) # ... Included Processors: The processors included with this package are: LoadTimeProcessor: exec's code at gobble time. This is primarily for Python code which should be executed only 1 time, such as opening file descriptors, etc. Syntax: <+i = 10+> ExecProcessor: exec's code at process time. Output from the executed code is discarded. Syntax: <|import time|> EvalProcessor: evaluates an expression at process time. The return value from the evaluated code is written to the output stream. Syntax: <:time.ctime(time.time()):> """ import sys import string try: import cStringIO StringIO = cStringIO except: import StringIO START_DELIMITER = '<' END_DELIMITER = '>' StringType = type('') Emb_FileName = '' class BasicProcessor: """BasicProcessor: Base class for simple embedded processors.""" def gobble(self, global_ns, data, start_idx, end_idx): """gobble(global_ns, data, start_idx, end_idx): Snag all the data up to the end token delimiter. When the data is snagged, it will be passed to get_code()""" new_idx = string.find(data, self.token + END_DELIMITER, start_idx, end_idx) if new_idx == -1: raise 'failed to find end token %s' % (self.token + END_DELIMITER,) badd = self.get_code(global_ns, data[start_idx + 2:new_idx]) return new_idx + 2, badd class LoadTimeProcessor(BasicProcessor): token = '+' def get_code(self, global_ns, data): exec(data, global_ns) return "" class ExecProcessor(BasicProcessor): token = '|' def get_code(self, global_ns, data): return data + "\n" class EvalProcessor(BasicProcessor): token = ':' def get_code(self, global_ns, data): return """write(str(%s))""" % data class TextProcessor: def __init__(self): self.source = "" self.processors = {} self.code = None def register_processor(self, processor): token = processor.token self.processors[token] = processor def gobble(self, global_ns, data, start_idx, end_idx): self.code = None curr_idx = start_idx textblock_begin = curr_idx processors = self.processors fake_endidx = end_idx - 2 while 1: new_idx = string.find(data, START_DELIMITER, curr_idx, fake_endidx) if new_idx == -1: break token = data[new_idx + 1] if not processors.has_key(token): curr_idx = new_idx + 1 continue # Else, save current text block, and process the rest if textblock_begin != new_idx: self.source = "%s\nwrite(%s)\n" % ( self.source, `data[textblock_begin:new_idx]`) processor = processors[token]() curr_idx, badd = processor.gobble(global_ns, data,new_idx, end_idx) textblock_begin = curr_idx if badd: self.source = "%s\n%s" % (self.source, badd) if textblock_begin != end_idx: self.source = "%s\nwrite(%s)\n" % (self.source, `data[textblock_begin:]`) def process(self, global_ns, write): global_ns['write'] = write if not self.code: self.code = compile(self.source, Emb_FileName, 'exec') exec(self.code, global_ns) def benchit(): x = TextProcessor() x.register_processor(EvalProcessor) x.register_processor(ExecProcessor) x.register_processor(LoadTimeProcessor) data = """<+i = 10+><+import time+><+import sys+> Hello world. The time is: <:time.ctime(time.time()):> Text is converted, easily in this model. Howabout that? Count is at: <:i:><|i=i+1|>""" global_ns = {} x.gobble(global_ns, data, 0, len(data)) for i in range(20000): outfile = StringIO.StringIO() x.process(global_ns, outfile.write) if __name__ == '__main__': if len(sys.argv) != 2: import profile profile.run('benchit()') sys.exit(-1) x = TextProcessor() x.register_processor(EvalProcessor) x.register_processor(ExecProcessor) x.register_processor(LoadTimeProcessor) data = open(sys.argv[1]).read() global_ns = {} x.gobble(global_ns, data, 0, len(data)) x.process(global_ns, sys.stdout.write)