/pytof/ezt.py
Python | 206 lines | 136 code | 22 blank | 48 comment | 38 complexity | dcf5fdc25c605f42a0828c176f39dc00 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, GPL-3.0, GPL-2.0, IPL-1.0
- #
- # ezt.py -- easy templating
- #
- # Copyright (C) 2001 Greg Stein. All Rights Reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are
- # met:
- #
- # * Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- #
- # * Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- # POSSIBILITY OF SUCH DAMAGE.
- #
- #
- # This software is maintained by Greg and is available at:
- # http://edna.sourceforge.net/
- #
- import string
- import re
- from types import StringType, IntType, FloatType
- _re_parse = re.compile('(\[[-\w.# ]+\])|(\[\[\])')
- # block commands and their argument counts
- _block_cmd_specs = { 'if-any':1, 'if-index':2, 'for':1 }
- _block_cmds = _block_cmd_specs.keys()
- class Template:
- def __init__(self, fname=None):
- if fname:
- self.parse_file(fname)
- def parse_file(self, fname):
- self.parse(open(fname).read())
- def parse(self, text):
- # parse the program into: (TEXT DIRECTIVE BRACKET)* TEXT
- # DIRECTIVE will be '[directive]' or None
- # BRACKET will be '[[]' or None
- parts = _re_parse.split(text)
- program = [ ]
- stack = [ ]
- for i in range(len(parts)):
- piece = parts[i]
- which = i % 3 # discriminate between: TEXT DIRECTIVE BRACKET
- if which == 0:
- # TEXT. append if non-empty.
- if piece:
- program.append(piece)
- elif which == 2:
- # BRACKET directive. append '[' if present.
- if piece:
- program.append('[')
- elif piece:
- # DIRECTIVE is present.
- args = string.split(piece[1:-1])
- cmd = args[0]
- if cmd == '#':
- # comment
- continue
- if cmd == 'else':
- if len(args) > 1:
- raise ArgCountSyntaxError()
- ### check: don't allow for 'for' cmd
- idx = stack[-1][1]
- true_section = program[idx:]
- del program[idx:]
- stack[-1][3] = true_section
- elif cmd == 'end':
- if len(args) > 1:
- raise ArgCountSyntaxError()
- # note: true-section may be None
- cmd, idx, args, true_section = stack.pop()
- else_section = program[idx:]
- func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
- program[idx:] = [ (func, (args, true_section, else_section)) ]
- elif cmd in _block_cmds:
- if len(args) > _block_cmd_specs[cmd] + 1:
- raise ArgCountSyntaxError()
- ### this assumes arg1 is always a ref
- args[1] = _prepare_ref(args[1])
- # remember the cmd, current pos, args, and a section placeholder
- stack.append([cmd, len(program), args[1:], None])
- else:
- # implied PRINT command
- if len(args) > 1:
- raise ArgCountSyntaxError()
- program.append((self._cmd_print, _prepare_ref(args[0])))
- self.program = program
- def generate(self, fp, data):
- ctx = _context()
- ctx.data = data
- ctx.for_index = { }
- self._execute(self.program, fp, ctx)
- def _execute(self, program, fp, ctx):
- for step in program:
- if isinstance(step, StringType):
- fp.write(step)
- else:
- step[0](step[1], fp, ctx)
- def _cmd_print(self, (refname, ref), fp, ctx):
- ### type check the value
- fp.write(_get_value(refname, ref, ctx))
- def _cmd_if_any(self, args, fp, ctx):
- "If the value is a non-empty string or non-empty list, then T else F."
- (((refname, ref),), t_section, f_section) = args
- value = _get_value(refname, ref, ctx)
- self._do_if(value, t_section, f_section, fp, ctx)
- def _cmd_if_index(self, args, fp, ctx):
- (((refname, ref), value), t_section, f_section) = args
- list, idx = ctx.for_index[refname]
- if value == 'even':
- value = idx % 2 == 0
- elif value == 'odd':
- value = idx % 2 == 1
- elif value == 'last':
- value = idx == len(list)-1
- else:
- value = idx == int(value)
- self._do_if(value, t_section, f_section, fp, ctx)
- def _do_if(self, value, t_section, f_section, fp, ctx):
- if t_section is None:
- t_section = f_section
- f_section = None
- if value:
- section = t_section
- else:
- section = f_section
- if section is not None:
- self._execute(section, fp, ctx)
- def _cmd_for(self, args, fp, ctx):
- (((refname, ref),), unused, section) = args
- list = _get_value(refname, ref, ctx)
- if isinstance(list, StringType):
- raise NeedSequenceError()
- ctx.for_index[refname] = [ list, 0 ]
- for i in range(len(list)):
- ctx.for_index[refname][1] = i
- self._execute(section, fp, ctx)
- del ctx.for_index[refname]
- def _prepare_ref(refname):
- return refname, string.split(refname, '.')
- def _get_value(refname, ref, ctx):
- if ctx.for_index.has_key(ref[0]):
- list, idx = ctx.for_index[ref[0]]
- ob = list[idx]
- elif ctx.data.has_key(ref[0]):
- ob = ctx.data[ref[0]]
- else:
- raise UnknownReference(refname)
- # walk the dotted ref
- for attr in ref[1:]:
- try:
- ob = getattr(ob, attr)
- except AttributeError:
- raise UnknownReference(refname)
- # make sure we return a string. ### other types?
- if isinstance(ob, IntType) or isinstance(ob, FloatType):
- return str(ob)
- if ob is None:
- return ''
- return ob
- class _context:
- pass
- class ArgCountSyntaxError(Exception):
- pass
- class UnknownReference(Exception):
- pass
- class NeedSequenceError(Exception):
- pass