Chris PeBenito 89ec23
"""PyPlate : a simple Python-based templating program
Chris PeBenito 89ec23
Chris PeBenito 89ec23
PyPlate parses a file and replaces directives (in double square brackets [[ ... ]])
Chris PeBenito 89ec23
by various means using a given dictionary of variables.  Arbitrary Python code
Chris PeBenito 89ec23
can be run inside many of the directives, making this system highly flexible.
Chris PeBenito 89ec23
Chris PeBenito 89ec23
Usage:
Chris PeBenito 89ec23
# Load and parse template file
Chris PeBenito 89ec23
template = pyplate.Template("output") (filename or string)
Chris PeBenito 89ec23
# Execute it with a dictionary of variables
Chris PeBenito 89ec23
template.execute_file(output_stream, locals())
Chris PeBenito 89ec23
Chris PeBenito 89ec23
PyPlate defines the following directives:
Chris PeBenito 89ec23
  [[...]]       evaluate the arbitrary Python expression and insert the
Chris PeBenito 89ec23
                result into the output
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[# ... #]]   comment.
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[exec ...]]  execute arbitrary Python code in the sandbox namespace
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[if ...]]    conditional expressions with usual Python semantics
Chris PeBenito 89ec23
  [[elif ...]]
Chris PeBenito 89ec23
  [[else]]
Chris PeBenito 89ec23
  [[end]]
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[for ... in ...]]  for-loop with usual Python semantics
Chris PeBenito 89ec23
  [[end]]
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[def ...(...)]]  define a "function" out of other templating elements
Chris PeBenito 89ec23
  [[end]]
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  [[call ...]]  call a templating function (not a regular Python function)
Chris PeBenito 89ec23
"""
Chris PeBenito 89ec23
Chris PeBenito 89ec23
#
Chris PeBenito 89ec23
# Copyright (C) 2002 Michael Droettboom
Chris PeBenito 89ec23
#
Chris PeBenito 89ec23
# This program is free software; you can redistribute it and/or
Chris PeBenito 89ec23
# modify it under the terms of the GNU General Public License
Chris PeBenito 89ec23
# as published by the Free Software Foundation; either version 2
Chris PeBenito 89ec23
# of the License, or (at your option) any later version.
Chris PeBenito 89ec23
#
Chris PeBenito 89ec23
# This program is distributed in the hope that it will be useful,
Chris PeBenito 89ec23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris PeBenito 89ec23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Chris PeBenito 89ec23
# GNU General Public License for more details.
Chris PeBenito 89ec23
# 
Chris PeBenito 89ec23
# You should have received a copy of the GNU General Public License
Chris PeBenito 89ec23
# along with this program; if not, write to the Free Software
Chris PeBenito 89ec23
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
Chris PeBenito 89ec23
#
Chris PeBenito 89ec23
Chris PeBenito 89ec23
from __future__ import nested_scopes
Chris PeBenito 89ec23
import sys, string, re, cStringIO
Chris PeBenito 89ec23
Chris PeBenito 89ec23
re_directive = re.compile("\[\[(.*)\]\]")
Chris PeBenito 89ec23
re_for_loop = re.compile("for (.*) in (.*)")
Chris PeBenito 89ec23
re_if = re.compile("if (.*)")
Chris PeBenito 89ec23
re_elif = re.compile("elif (.*)")
Chris PeBenito 89ec23
re_def = re.compile("def (.*?)\((.*)\)")
Chris PeBenito 89ec23
re_call = re.compile("call (.*?)\((.*)\)")
Chris PeBenito 89ec23
re_exec = re.compile("exec (.*)")
Chris PeBenito 89ec23
re_comment = re.compile("#(.*)#")
Chris PeBenito 89ec23
Chris PeBenito 89ec23
############################################################
Chris PeBenito 89ec23
# Template parser
Chris PeBenito 89ec23
class ParserException(Exception):
Chris PeBenito 89ec23
  def __init__(self, lineno, s):
Chris PeBenito 89ec23
    Exception.__init__(self, "line %d: %s" % (lineno, s))
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class Template:
Chris PeBenito 89ec23
  def __init__(self, filename=None):
Chris PeBenito 89ec23
    if filename != None:
Chris PeBenito 89ec23
      try:
Chris PeBenito 89ec23
        self.parse_file(filename)
Chris PeBenito 89ec23
      except:
Chris PeBenito 89ec23
        self.parse_string(filename)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parse_file(self, filename):
Chris PeBenito 89ec23
    file = open(filename, 'r')
Chris PeBenito 89ec23
    self.parse(file)
Chris PeBenito 89ec23
    file.close()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parse_string(self, template):
Chris PeBenito 89ec23
    file = cStringIO.StringIO(template)
Chris PeBenito 89ec23
    self.parse(file)
Chris PeBenito 89ec23
    file.close()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parse(self, file):
Chris PeBenito 89ec23
    self.file = file
Chris PeBenito 89ec23
    self.line = self.file.read()
Chris PeBenito 89ec23
    self.lineno = 0
Chris PeBenito 89ec23
    self.functions = {}
Chris PeBenito 89ec23
    self.tree = TopLevelTemplateNode(self)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parser_get(self):
Chris PeBenito 89ec23
    if self.line == '':
Chris PeBenito 89ec23
      return None
Chris PeBenito 89ec23
    return self.line
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parser_eat(self, chars):
Chris PeBenito 89ec23
    self.lineno = self.lineno + self.line[:chars].count("\n")
Chris PeBenito 89ec23
    self.line = self.line[chars:]
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def parser_exception(self, s):
Chris PeBenito 89ec23
    raise ParserException(self.lineno, s)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute_file(self, filename, data):
Chris PeBenito 89ec23
    file = open(filename, 'w')
Chris PeBenito 89ec23
    self.execute(file, data)
Chris PeBenito 89ec23
    file.close()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute_string(self, data):
Chris PeBenito 89ec23
    s = cStringIO.StringIO()
Chris PeBenito 89ec23
    self.execute(s, data)
Chris PeBenito 89ec23
    return s.getvalue()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute_stdout(self, data):
Chris PeBenito 89ec23
    self.execute(sys.stdout, data)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream=sys.stdout, data={}):
Chris PeBenito 89ec23
    self.tree.execute(stream, data)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def __repr__(self):
Chris PeBenito 89ec23
    return repr(self.tree)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
Chris PeBenito 89ec23
############################################################
Chris PeBenito 89ec23
# NODES
Chris PeBenito 89ec23
class TemplateNode:
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    self.parent = parent
Chris PeBenito 89ec23
    self.s = s
Chris PeBenito 89ec23
    self.node_list = []
Chris PeBenito 89ec23
    while 1:
Chris PeBenito 89ec23
      new_node = TemplateNodeFactory(parent)
Chris PeBenito 89ec23
      if self.add_node(new_node):
Chris PeBenito 89ec23
        break
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def add_node(self, node):
Chris PeBenito 89ec23
    if node == 'end':
Chris PeBenito 89ec23
      return 1
Chris PeBenito 89ec23
    elif node != None:
Chris PeBenito 89ec23
      self.node_list.append(node)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      raise self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] does not have a matching [[end]]" % self.s)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    for node in self.node_list:
Chris PeBenito 89ec23
      node.execute(stream, data)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def __repr__(self):
Chris PeBenito 89ec23
    r = "<" + self.__class__.__name__ + " "
Chris PeBenito 89ec23
    for i in self.node_list:
Chris PeBenito 89ec23
      r = r + repr(i)
Chris PeBenito 89ec23
    r = r + ">"
Chris PeBenito 89ec23
    return r
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class TopLevelTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent):
Chris PeBenito 89ec23
    TemplateNode.__init__(self, parent, '')
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def add_node(self, node):
Chris PeBenito 89ec23
    if node != None:
Chris PeBenito 89ec23
      self.node_list.append(node)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      return 1
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class ForTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    TemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_for_loop.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      raise self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid for-loop expression" % self.s)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      self.vars_temp = match.group(1).split(",")
Chris PeBenito 89ec23
      self.vars = []
Chris PeBenito 89ec23
      for v in self.vars_temp:
Chris PeBenito 89ec23
        self.vars.append(v.strip())
Chris PeBenito 5a3299
      #print self.vars
Chris PeBenito 89ec23
      self.expression = match.group(2)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    remember_vars = {}
Chris PeBenito 89ec23
    for var in self.vars:
Chris PeBenito 89ec23
      if data.has_key(var):
Chris PeBenito 89ec23
        remember_vars[var] = data[var]
Chris PeBenito 89ec23
    for list in eval(self.expression, globals(), data):
Chris PeBenito 5a3299
      if is_sequence(list):
Chris PeBenito 5a3299
        for index, value in enumerate(list):
Chris PeBenito 89ec23
          data[self.vars[index]] = value
Chris PeBenito 89ec23
      else:
Chris PeBenito 89ec23
        data[self.vars[0]] = list
Chris PeBenito 89ec23
      TemplateNode.execute(self, stream, data)
Chris PeBenito 89ec23
    for key, value in remember_vars.items():
Chris PeBenito 89ec23
      data[key] = value
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class IfTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    self.else_node = None
Chris PeBenito 89ec23
    TemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_if.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      raise self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid if expression" % self.s)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      self.expression = match.group(1)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def add_node(self, node):
Chris PeBenito 89ec23
    if node == 'end':
Chris PeBenito 89ec23
      return 1
Chris PeBenito 89ec23
    elif isinstance(node, ElseTemplateNode):
Chris PeBenito 89ec23
      self.else_node = node
Chris PeBenito 89ec23
      return 1
Chris PeBenito 89ec23
    elif isinstance(node, ElifTemplateNode):
Chris PeBenito 89ec23
      self.else_node = node
Chris PeBenito 89ec23
      return 1
Chris PeBenito 89ec23
    elif node != None:
Chris PeBenito 89ec23
      self.node_list.append(node)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      raise self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] does not have a matching [[end]]" % self.s)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    if eval(self.expression, globals(), data):
Chris PeBenito 89ec23
      TemplateNode.execute(self, stream, data)
Chris PeBenito 89ec23
    elif self.else_node != None:
Chris PeBenito 89ec23
      self.else_node.execute(stream, data)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class ElifTemplateNode(IfTemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    self.else_node = None
Chris PeBenito 89ec23
    TemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_elif.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid elif expression" % self.s)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      self.expression = match.group(1)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class ElseTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  pass
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class FunctionTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    TemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_def.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid function definition" % self.s)
Chris PeBenito 89ec23
    self.function_name = match.group(1)
Chris PeBenito 89ec23
    self.vars_temp = match.group(2).split(",")
Chris PeBenito 89ec23
    self.vars = []
Chris PeBenito 89ec23
    for v in self.vars_temp:
Chris PeBenito 89ec23
      self.vars.append(v.strip())
Chris PeBenito 5a3299
    #print self.vars
Chris PeBenito 89ec23
    self.parent.functions[self.function_name] = self
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    pass
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def call(self, args, stream, data):
Chris PeBenito 89ec23
    remember_vars = {}
Chris PeBenito 5a3299
    for index, var in enumerate(self.vars):
Chris PeBenito 89ec23
      if data.has_key(var):
Chris PeBenito 89ec23
        remember_vars[var] = data[var]
Chris PeBenito 89ec23
      data[var] = args[index]
Chris PeBenito 89ec23
    TemplateNode.execute(self, stream, data)
Chris PeBenito 89ec23
    for key, value in remember_vars.items():
Chris PeBenito 89ec23
      data[key] = value
Chris PeBenito 89ec23
      
Chris PeBenito 89ec23
class LeafTemplateNode(TemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    self.parent = parent
Chris PeBenito 89ec23
    self.s = s
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    stream.write(self.s)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def __repr__(self):
Chris PeBenito 89ec23
    return "<" + self.__class__.__name__ + ">"
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class CommentTemplateNode(LeafTemplateNode):
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    pass
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class ExpressionTemplateNode(LeafTemplateNode):
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    stream.write(str(eval(self.s, globals(), data)))
Chris PeBenito 89ec23
Chris PeBenito 89ec23
class ExecTemplateNode(LeafTemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    LeafTemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_exec.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid statement" % self.s)
Chris PeBenito 89ec23
    self.s = match.group(1)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    exec(self.s, globals(), data)
Chris PeBenito 89ec23
    pass
Chris PeBenito 89ec23
    
Chris PeBenito 89ec23
class CallTemplateNode(LeafTemplateNode):
Chris PeBenito 89ec23
  def __init__(self, parent, s):
Chris PeBenito 89ec23
    LeafTemplateNode.__init__(self, parent, s)
Chris PeBenito 89ec23
    match = re_call.match(s)
Chris PeBenito 89ec23
    if match == None:
Chris PeBenito 89ec23
      self.parent.parser_exception(
Chris PeBenito 89ec23
        "[[%s]] is not a valid function call" % self.s)
Chris PeBenito 89ec23
    self.function_name = match.group(1)
Chris PeBenito 89ec23
    self.vars = "(" + match.group(2).strip() + ",)"
Chris PeBenito 89ec23
  
Chris PeBenito 89ec23
  def execute(self, stream, data):
Chris PeBenito 89ec23
    self.parent.functions[self.function_name].call(
Chris PeBenito 89ec23
      eval(self.vars, globals(), data), stream, data)
Chris PeBenito 89ec23
Chris PeBenito 89ec23
Chris PeBenito 89ec23
############################################################
Chris PeBenito 89ec23
# Node factory
Chris PeBenito 89ec23
template_factory_type_map = {
Chris PeBenito 89ec23
  'if'   : IfTemplateNode,
Chris PeBenito 89ec23
  'for'  : ForTemplateNode,
Chris PeBenito 89ec23
  'elif' : ElifTemplateNode,
Chris PeBenito 89ec23
  'else' : ElseTemplateNode,
Chris PeBenito 89ec23
  'def'  : FunctionTemplateNode,
Chris PeBenito 89ec23
  'call' : CallTemplateNode,
Chris PeBenito 89ec23
  'exec' : ExecTemplateNode }
Chris PeBenito 89ec23
template_factory_types = template_factory_type_map.keys()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
def TemplateNodeFactory(parent):
Chris PeBenito 89ec23
  src = parent.parser_get()
Chris PeBenito 89ec23
Chris PeBenito 89ec23
  if src == None:
Chris PeBenito 89ec23
    return None
Chris PeBenito 89ec23
  match = re_directive.search(src)
Chris PeBenito 89ec23
  if match == None:
Chris PeBenito 89ec23
    parent.parser_eat(len(src))
Chris PeBenito 89ec23
    return LeafTemplateNode(parent, src)
Chris PeBenito 89ec23
  elif src == '' or match.start() != 0:
Chris PeBenito 89ec23
    parent.parser_eat(match.start())
Chris PeBenito 89ec23
    return LeafTemplateNode(parent, src[:match.start()])
Chris PeBenito 89ec23
  else:
Chris PeBenito 89ec23
    directive = match.group()[2:-2].strip()
Chris PeBenito 89ec23
    parent.parser_eat(match.end())
Chris PeBenito 89ec23
    if directive == 'end':
Chris PeBenito 89ec23
      return 'end'
Chris PeBenito 89ec23
    elif re_comment.match(directive):
Chris PeBenito 89ec23
      return CommentTemplateNode(parent, directive)
Chris PeBenito 89ec23
    else:
Chris PeBenito 89ec23
      for i in template_factory_types:
Chris PeBenito 89ec23
        if directive[0:len(i)] == i:
Chris PeBenito 89ec23
          return template_factory_type_map[i](parent, directive)
Chris PeBenito 89ec23
      return ExpressionTemplateNode(parent, directive)
Chris PeBenito 89ec23
Chris PeBenito 5a3299
def is_sequence(object):
Chris PeBenito 5a3299
  try:
Chris PeBenito 5a3299
    test = object[0:0]
Chris PeBenito 5a3299
  except:
Chris PeBenito 5a3299
    return False
Chris PeBenito 5a3299
  else:
Chris PeBenito 5a3299
    return True