web: switch generation script to python 3 (#775802)

This commit is contained in:
Frédéric Péters 2023-03-25 08:36:16 +01:00
parent d457886486
commit 5b78304f42
2 changed files with 475 additions and 389 deletions

View File

@ -1,10 +1,10 @@
#! /usr/bin/env python
#! /usr/bin/env python3
import xml.dom.minidom
import os
import stat
import re
from six import StringIO
from io import StringIO
import sys
import ezt
@ -318,4 +318,3 @@ for base, dirs, files in os.walk('web'):
with open(dst_file, 'w')as f:
f.write(fd.getvalue())
continue

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
"""ezt.py -- easy templating
ezt templates are simply text files in whatever format you so desire
@ -224,12 +223,11 @@ Directives
# http://svn.webdav.org/repos/projects/ezt/trunk/
#
import string
import re
from types import StringType, IntType, FloatType, LongType
import datetime
import html
import io
import os
import cgi
from six import StringIO
import re
#
# Formatting types
@ -237,6 +235,7 @@ from six import StringIO
FORMAT_RAW = 'raw'
FORMAT_HTML = 'html'
FORMAT_XML = 'xml'
FORMAT_RTF = 'rtf'
#
# This regular expression matches three alternatives:
@ -259,7 +258,7 @@ _re_parse = re.compile(r'\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % (_item, _item
_re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+')
# block commands and their argument counts
_block_cmd_specs = { 'if-index':2, 'for':1, 'is':2, 'define':1, 'format':1 }
_block_cmd_specs = {'if-index': 2, 'for': 1, 'is': 2, 'define': 1, 'format': 1}
_block_cmds = _block_cmd_specs.keys()
# two regular expresssions for compressing whitespace. the first is used to
@ -274,464 +273,552 @@ _re_whitespace = re.compile(r'\s\s+')
# an integer.
_re_subst = re.compile('%(%|[0-9]+)')
class Template:
_printers = {
FORMAT_RAW : '_cmd_print',
FORMAT_HTML : '_cmd_print_html',
FORMAT_XML : '_cmd_print_xml',
class Template:
_printers = {
FORMAT_RAW: '_cmd_print',
FORMAT_HTML: '_cmd_print_html',
FORMAT_XML: '_cmd_print_xml',
FORMAT_RTF: '_cmd_print_rtf',
}
def __init__(self, fname=None, compress_whitespace=1,
base_format=FORMAT_RAW):
self.compress_whitespace = compress_whitespace
if fname:
self.parse_file(fname, base_format)
def __init__(self, fname=None, compress_whitespace=1, base_format=FORMAT_RAW):
self.compress_whitespace = compress_whitespace
if fname:
self.parse_file(fname, base_format)
def parse_file(self, fname, base_format=FORMAT_RAW):
"fname -> a string object with pathname of file containg an EZT template."
def parse_file(self, fname, base_format=FORMAT_RAW):
"fname -> a string object with pathname of file containg an EZT template."
self.parse(_FileReader(fname), base_format)
self.parse(_FileReader(fname), base_format)
def parse(self, text_or_reader, base_format=FORMAT_RAW):
"""Parse the template specified by text_or_reader.
def parse(self, text_or_reader, base_format=FORMAT_RAW):
"""Parse the template specified by text_or_reader.
The argument should be a string containing the template, or it should
specify a subclass of ezt.Reader which can read templates. The base
format for printing values is given by base_format.
"""
if not isinstance(text_or_reader, Reader):
# assume the argument is a plain text string
text_or_reader = _TextReader(text_or_reader)
The argument should be a string containing the template, or it should
specify a subclass of ezt.Reader which can read templates. The base
format for printing values is given by base_format.
"""
if not isinstance(text_or_reader, Reader):
# assume the argument is a plain text string
text_or_reader = _TextReader(text_or_reader)
printer = getattr(self, self._printers[base_format])
self.program = self._parse(text_or_reader, base_printer=printer)
printer = getattr(self, self._printers[base_format])
self.program = self._parse(text_or_reader, base_printer=printer)
def generate(self, fp, data):
if hasattr(data, '__getitem__') or callable(getattr(data, 'keys', None)):
# a dictionary-like object was passed. convert it to an
# attribute-based object.
class _data_ob:
def __init__(self, d):
vars(self).update(d)
data = _data_ob(data)
def generate(self, fp, data):
if hasattr(data, '__getitem__') or callable(getattr(data, 'keys', None)):
# a dictionary-like object was passed. convert it to an
# attribute-based object.
class _data_ob:
def __init__(self, d):
self.data = d
ctx = _context()
ctx.data = data
ctx.for_index = { }
ctx.defines = { }
self._execute(self.program, fp, ctx)
def __getattr__(self, k):
try:
return self.data[k]
except KeyError:
raise AttributeError(k)
def _parse(self, reader, for_names=None, file_args=(), base_printer=None):
"""text -> string object containing the template.
data = _data_ob(data)
This is a private helper function doing the real work for method parse.
It returns the parsed template as a 'program'. This program is a sequence
made out of strings or (function, argument) 2-tuples.
ctx = _context()
ctx.data = data
ctx.for_index = {}
ctx.defines = {}
self._execute(self.program, fp, ctx)
Note: comment directives [# ...] are automatically dropped by _re_parse.
"""
def _parse(self, reader, for_names=None, file_args=(), base_printer=None):
"""text -> string object containing the template.
# parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT
parts = _re_parse.split(reader.text)
This is a private helper function doing the real work for method parse.
It returns the parsed template as a 'program'. This program is a sequence
made out of strings or (function, argument) 2-tuples.
program = [ ]
stack = [ ]
if not for_names:
for_names = [ ]
Note: comment directives [# ...] are automatically dropped by _re_parse.
"""
if base_printer:
printers = [ base_printer ]
else:
printers = [ self._cmd_print ]
# parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT
parts = _re_parse.split(reader.text)
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:
if self.compress_whitespace:
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece))
program.append(piece)
elif which == 2:
# BRACKET directive. append '[' if present.
if piece:
program.append('[')
elif piece:
# DIRECTIVE is present.
args = _re_args.findall(piece)
cmd = args[0]
if cmd == 'else':
if len(args) > 1:
raise ArgCountSyntaxError(str(args[1:]))
### 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(str(args[1:]))
# note: true-section may be None
try:
cmd, idx, args, true_section = stack.pop()
except IndexError:
raise UnmatchedEndError()
else_section = program[idx:]
if cmd == 'format':
printers.pop()
else:
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
program[idx:] = [ (func, (args, true_section, else_section)) ]
if cmd == 'for':
for_names.pop()
elif cmd in _block_cmds:
if len(args) > _block_cmd_specs[cmd] + 1:
raise ArgCountSyntaxError(str(args[1:]))
### this assumes arg1 is always a ref unless cmd is 'define'
if cmd != 'define':
args[1] = _prepare_ref(args[1], for_names, file_args)
program = []
stack = []
if not for_names:
for_names = []
# handle arg2 for the 'is' command
if cmd == 'is':
args[2] = _prepare_ref(args[2], for_names, file_args)
elif cmd == 'for':
for_names.append(args[1][0]) # append the refname
elif cmd == 'format':
if args[1][0]:
raise BadFormatConstantError(str(args[1:]))
funcname = self._printers.get(args[1][1])
if not funcname:
raise UnknownFormatConstantError(str(args[1:]))
printers.append(getattr(self, funcname))
# remember the cmd, current pos, args, and a section placeholder
stack.append([cmd, len(program), args[1:], None])
elif cmd == 'include':
if args[1][0] == '"':
include_filename = args[1][1:-1]
f_args = [ ]
for arg in args[2:]:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.extend(self._parse(reader.read_other(include_filename),
for_names, f_args, printers[-1]))
else:
if len(args) != 2:
raise ArgCountSyntaxError(str(args))
program.append((self._cmd_include,
(_prepare_ref(args[1], for_names, file_args),
reader)))
elif cmd == 'if-any':
f_args = [ ]
for arg in args[1:]:
f_args.append(_prepare_ref(arg, for_names, file_args))
stack.append(['if-any', len(program), f_args, None])
if base_printer:
printers = [base_printer]
else:
# implied PRINT command
f_args = [ ]
for arg in args:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.append((printers[-1], f_args))
printers = [self._cmd_print]
if stack:
### would be nice to say which blocks...
raise UnclosedBlocksError()
return program
column = 0
line = 0
def _execute(self, program, fp, ctx):
"""This private helper function takes a 'program' sequence as created
by the method '_parse' and executes it step by step. strings are written
to the file object 'fp' and functions are called.
"""
for step in program:
if isinstance(step, StringType):
fp.write(step)
else:
step[0](step[1], fp, ctx)
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.
line += piece.count('\n')
last_newline = piece.rfind('\n')
if last_newline != -1:
column = 0
column += len(piece) - last_newline - 1
if piece:
if self.compress_whitespace:
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece))
program.append(piece)
elif which == 2:
# BRACKET directive. append '[' if present.
if piece:
program.append('[')
elif piece:
# DIRECTIVE is present.
args = _re_args.findall(piece)
cmd = args[0]
if cmd == 'else':
if len(args) > 1:
raise ArgCountSyntaxError(str(args[1:]), line, column)
# check: don't allow for 'for' cmd
try:
idx = stack[-1][1]
except IndexError:
raise UnmatchedElseError('', line, column)
true_section = program[idx:]
del program[idx:]
stack[-1][3] = true_section
elif cmd == 'end':
if len(args) > 1:
raise ArgCountSyntaxError(str(args[1:]), line, column)
# note: true-section may be None
try:
cmd, idx, args, true_section = stack.pop()
except IndexError:
raise UnmatchedEndError('', line, column)
else_section = program[idx:]
if cmd == 'format':
printers.pop()
else:
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
program[idx:] = [(func, (args, true_section, else_section))]
if cmd == 'for':
for_names.pop()
elif cmd in _block_cmds:
if len(args) > _block_cmd_specs[cmd] + 1:
raise ArgCountSyntaxError(str(args[1:]), line, column)
# this assumes arg1 is always a ref unless cmd is 'define'
if cmd != 'define':
if len(args) < 2:
raise ArgCountSyntaxError(str(args), line, column)
args[1] = _prepare_ref(args[1], for_names, file_args)
def _cmd_print(self, valref, fp, ctx):
_write_value(valref, fp, ctx)
# handle arg2 for the 'is' command
if cmd == 'is':
if len(args) != 3:
raise ArgCountSyntaxError(str(args[1:]), line, column)
args[2] = _prepare_ref(args[2], for_names, file_args)
elif cmd == 'for':
for_names.append(args[1][0]) # append the refname
elif cmd == 'format':
if args[1][0]:
raise BadFormatConstantError(str(args[1:]), line, column)
funcname = self._printers.get(args[1][1])
if not funcname:
raise UnknownFormatConstantError(str(args[1:]), line, column)
printers.append(getattr(self, funcname))
def _cmd_print_html(self, valref, fp, ctx):
_write_value(valref, fp, ctx, cgi.escape)
# remember the cmd, current pos, args, and a section placeholder
stack.append([cmd, len(program), args[1:], None])
elif cmd == 'include':
if args[1][0] == '"':
include_filename = args[1][1:-1]
f_args = []
for arg in args[2:]:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.extend(
self._parse(reader.read_other(include_filename), for_names, f_args, printers[-1])
)
else:
if len(args) != 2:
raise ArgCountSyntaxError(str(args), line, column)
program.append(
(self._cmd_include, (_prepare_ref(args[1], for_names, file_args), reader))
)
elif cmd == 'if-any':
f_args = []
for arg in args[1:]:
f_args.append(_prepare_ref(arg, for_names, file_args))
stack.append(['if-any', len(program), f_args, None])
else:
# implied PRINT command
f_args = []
for arg in args:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.append((printers[-1], f_args))
column += 2 + len(piece)
def _cmd_print_xml(self, valref, fp, ctx):
### use the same quoting as HTML for now
self._cmd_print_html(valref, fp, ctx)
if stack:
# would be nice to say which blocks...
raise UnclosedBlocksError('', line, column)
return program
def _cmd_include(self, valref_reader_tuple, fp, ctx):
valref, reader = valref_reader_tuple
fname = _get_value(valref, ctx)
### note: we don't have the set of for_names to pass into this parse.
### I don't think there is anything to do but document it. we also
### don't have a current format (since that is a compile-time concept).
self._execute(self._parse(reader.read_other(fname)), fp, ctx)
def _execute(self, program, fp, ctx):
"""This private helper function takes a 'program' sequence as created
by the method '_parse' and executes it step by step. strings are written
to the file object 'fp' and functions are called.
"""
for step in program:
if isinstance(step, str):
fp.write(step)
else:
step[0](step[1], fp, ctx)
def _cmd_if_any(self, args, fp, ctx):
"If any value is a non-empty string or non-empty list, then T else F."
(valrefs, t_section, f_section) = args
value = 0
for valref in valrefs:
if _get_value(valref, ctx):
value = 1
break
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_print(self, valref, fp, ctx):
_write_value(valref, fp, ctx)
def _cmd_if_index(self, args, fp, ctx):
((valref, value), t_section, f_section) = args
list, idx = ctx.for_index[valref[0]]
if value == 'even':
value = idx % 2 == 0
elif value == 'odd':
value = idx % 2 == 1
elif value == 'first':
value = idx == 0
elif value == 'last':
value = idx == len(list)-1
else:
value = idx == int(value)
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_print_html(self, valref, fp, ctx):
_write_value(valref, fp, ctx, html.escape)
def _cmd_is(self, args, fp, ctx):
((left_ref, right_ref), t_section, f_section) = args
value = _get_value(right_ref, ctx)
value = string.lower(_get_value(left_ref, ctx)) == string.lower(value)
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_print_rtf(self, valref, fp, ctx):
def char2rtf(c):
if ord(c) < 128:
return c
else:
return '\\u%d?' % ord(c)
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 rtf_escape(s):
s = ''.join([char2rtf(c) for c in s])
return '{\\uc1{%s}}' % s
def _cmd_for(self, args, fp, ctx):
((valref,), unused, section) = args
list = _get_value(valref, ctx)
if isinstance(list, StringType):
raise NeedSequenceError()
refname = valref[0]
ctx.for_index[refname] = idx = [ list, 0 ]
for item in list:
self._execute(section, fp, ctx)
idx[1] = idx[1] + 1
del ctx.for_index[refname]
_write_value(valref, fp, ctx, rtf_escape)
def _cmd_print_xml(self, valref, fp, ctx):
# use the same quoting as HTML for now
self._cmd_print_html(valref, fp, ctx)
def _cmd_include(self, include_ref, fp, ctx):
(valref, reader) = include_ref
fname = _get_value(valref, ctx)
# note: we don't have the set of for_names to pass into this parse.
# I don't think there is anything to do but document it. we also
# don't have a current format (since that is a compile-time concept).
self._execute(self._parse(reader.read_other(fname)), fp, ctx)
def _cmd_if_any(self, args, fp, ctx):
"If any value is a non-empty string or non-empty list, then T else F."
(valrefs, t_section, f_section) = args
value = 0
for valref in valrefs:
try:
if _get_value(valref, ctx):
value = 1
break
except UnknownReference:
pass
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_if_index(self, args, fp, ctx):
((valref, value), t_section, f_section) = args
list, idx = ctx.for_index[valref[0]]
if value == 'even':
value = idx % 2 == 0
elif value == 'odd':
value = idx % 2 == 1
elif value == 'first':
value = idx == 0
elif value == 'last':
value = idx == len(list) - 1
else:
value = idx == int(value)
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_is(self, args, fp, ctx):
((left_ref, right_ref), t_section, f_section) = args
try:
value = _get_value(right_ref, ctx)
value = str(_get_value(left_ref, ctx)).lower() == str(value).lower()
except UnknownReference:
value = False
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):
((valref,), dummy, section) = args
try:
list = _get_value(valref, ctx)
except UnknownReference:
return
if isinstance(list, str):
raise NeedSequenceError()
refname = valref[0]
ctx.for_index[refname] = idx = [list, 0]
for dummy in list:
self._execute(section, fp, ctx)
idx[1] = idx[1] + 1
del ctx.for_index[refname]
def _cmd_define(self, args, fp, ctx):
((name,), dummy, section) = args
valfp = io.StringIO()
if section is not None:
self._execute(section, valfp, ctx)
ctx.defines[name] = valfp.getvalue()
def _cmd_define(self, args, fp, ctx):
((name,), unused, section) = args
valfp = StringIO()
if section is not None:
self._execute(section, valfp, ctx)
ctx.defines[name] = valfp.getvalue()
def boolean(value):
"Return a value suitable for [if-any bool_var] usage in a template."
if value:
return 'yes'
return None
"Return a value suitable for [if-any bool_var] usage in a template."
if value:
return 'yes'
return None
def _prepare_ref(refname, for_names, file_args):
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
for_names -> a list of active for sequences.
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
for_names -> a list of active for sequences.
Returns a `value reference', a 3-tuple made out of (refname, start, rest),
for fast access later.
"""
# is the reference a string constant?
if refname[0] == '"':
return None, refname[1:-1], None
Returns a `value reference', a 3-tuple made out of (refname, start, rest),
for fast access later.
"""
# is the reference a string constant?
if refname[0] == '"':
return None, refname[1:-1], None
parts = string.split(refname, '.')
start = parts[0]
rest = parts[1:]
parts = refname.split('.')
start = parts[0]
rest = parts[1:]
# if this is an include-argument, then just return the prepared ref
if start[:3] == 'arg':
try:
idx = int(start[3:])
except ValueError:
pass
# if this is an include-argument, then just return the prepared ref
if start[:3] == 'arg':
try:
idx = int(start[3:])
except ValueError:
pass
else:
if idx < len(file_args):
dummy, start, more_rest = file_args[idx]
if more_rest is None:
# the include-argument was a string constant
return None, start, None
# prepend the argument's "rest" for our further processing
rest[:0] = more_rest
# rewrite the refname to ensure that any potential 'for' processing
# has the correct name
# this can make it hard for debugging include files since we lose
# the 'argNNN' names
if not rest:
return start, start, []
refname = start + '.' + '.'.join(rest)
if for_names:
# From last to first part, check if this reference is part of a for loop
for i in range(len(parts), 0, -1):
name = '.'.join(parts[:i])
if name in for_names:
return refname, name, parts[i:]
return refname, start, rest
def _get_value(value_ref, ctx):
"""(refname, start, rest) -> a prepared `value reference' (see above).
ctx -> an execution context instance.
Does a name space lookup within the template name space. Active
for blocks take precedence over data dictionary members with the
same name.
"""
(refname, start, rest) = value_ref
if rest is None:
# it was a string constant
return start
# get the starting object
if start in ctx.for_index:
list, idx = ctx.for_index[start]
ob = list[idx]
elif start in ctx.defines:
ob = ctx.defines[start]
elif hasattr(ctx.data, start):
ob = getattr(ctx.data, start)
elif refname in ('True', 'False'):
return bool(refname == 'True')
else:
if idx < len(file_args):
orig_refname, start, more_rest = file_args[idx]
if more_rest is None:
# the include-argument was a string constant
return None, start, None
raise UnknownReference(refname)
# prepend the argument's "rest" for our further processing
rest[:0] = more_rest
# walk the rest of the dotted reference
for attr in rest:
try:
ob = getattr(ob, attr)
except AttributeError:
try:
ob = ob[attr]
except (TypeError, KeyError):
try:
ob = ob[int(attr)]
except (ValueError, TypeError):
raise UnknownReference(refname)
# rewrite the refname to ensure that any potential 'for' processing
# has the correct name
### this can make it hard for debugging include files since we lose
### the 'argNNN' names
if not rest:
return start, start, [ ]
refname = start + '.' + string.join(rest, '.')
# make sure we return a string instead of some various Python types
if isinstance(ob, (int, float)):
return str(ob)
if ob is None:
return ''
if for_names:
# From last to first part, check if this reference is part of a for loop
for i in range(len(parts), 0, -1):
name = string.join(parts[:i], '.')
if name in for_names:
return refname, name, parts[i:]
# string or a sequence
return ob
return refname, start, rest
def _get_value(refname_start_rest_tuple, ctx):
"""(refname, start, rest) -> a prepared `value reference' (see above).
ctx -> an execution context instance.
Does a name space lookup within the template name space. Active
for blocks take precedence over data dictionary members with the
same name.
"""
refname, start, rest = refname_start_rest_tuple
if rest is None:
# it was a string constant
return start
# get the starting object
if ctx.for_index.has_key(start):
list, idx = ctx.for_index[start]
ob = list[idx]
elif ctx.defines.has_key(start):
ob = ctx.defines[start]
elif hasattr(ctx.data, start):
ob = getattr(ctx.data, start)
else:
raise UnknownReference(refname)
# walk the rest of the dotted reference
for attr in rest:
def _get_value_fallback(value_ref, ctx):
try:
ob = getattr(ob, attr)
except AttributeError:
raise UnknownReference(refname)
return _get_value(value_ref, ctx)
except UnknownReference:
refname = value_ref[0]
return '[' + refname + ']'
# make sure we return a string instead of some various Python types
if isinstance(ob, IntType) \
or isinstance(ob, LongType) \
or isinstance(ob, FloatType):
return str(ob)
if ob is None:
return ''
# string or a sequence
return ob
def _write_value(valrefs, fp, ctx, format=lambda s: s):
value = _get_value(valrefs[0], ctx)
args = map(lambda valref, ctx=ctx: _get_value(valref, ctx), valrefs[1:])
try:
value = _get_value(valrefs[0], ctx)
except UnknownReference:
value = '[' + ' '.join([v[0] for v in valrefs]) + ']'
fp.write(format(value))
return
args = list(map(lambda valref, ctx=ctx: _get_value_fallback(valref, ctx), valrefs[1:]))
# if the value has a 'read' attribute, then it is a stream: copy it
if hasattr(value, 'read'):
while 1:
chunk = value.read(16384)
if not chunk:
break
fp.write(format(chunk))
# if the value has a 'read' attribute, then it is a stream: copy it
if hasattr(value, 'read'):
while True:
chunk = value.read(16384)
if not chunk:
break
fp.write(format(chunk))
# value is a callback function: call with file pointer and extra args
elif callable(value):
apply(value, [fp] + args)
# value is a substitution pattern
elif args:
parts = _re_subst.split(value)
for i in range(len(parts)):
piece = parts[i]
if i%2 == 1 and piece != '%':
idx = int(piece)
if idx < len(args):
piece = args[idx]
# value is a callback function
elif callable(value):
if getattr(value, 'ezt_call_mode', None) == 'simple':
# simple call mode, call with args and write the result
fp.write(value(*args))
else:
piece = '<undef>'
if format:
fp.write(format(piece))
# default, call with file pointer and extra args
value(*[fp] + args)
# plain old value, write to output
else:
fp.write(format(value))
# value is a substitution pattern
elif args:
parts = _re_subst.split(value)
for i in range(len(parts)):
piece = parts[i]
if i % 2 == 1 and piece != '%':
idx = int(piece)
if idx < len(args):
piece = args[idx]
else:
piece = '<undef>'
if format:
fp.write(format(piece))
elif isinstance(value, datetime.datetime):
from .misc import localstrftime
fp.write(localstrftime(value))
elif isinstance(value, datetime.date):
from .misc import date_format, strftime
fp.write(strftime(date_format(), value))
# plain old value, write to output
else:
fp.write(format(str(value)))
class _context:
"""A container for the execution context"""
"""A container for the execution context"""
class Reader:
"Abstract class which allows EZT to detect Reader objects."
"Abstract class which allows EZT to detect Reader objects."
class _FileReader(Reader):
"""Reads templates from the filesystem."""
def __init__(self, fname):
self.text = open(fname, 'rb').read()
self._dir = os.path.dirname(fname)
def read_other(self, relative):
return _FileReader(os.path.join(self._dir, relative))
"""Reads templates from the filesystem."""
def __init__(self, fname):
with open(fname, 'rb') as fd:
self.text = fd.read()
self._dir = os.path.dirname(fname)
def read_other(self, relative):
return _FileReader(os.path.join(self._dir, relative))
class _TextReader(Reader):
"""'Reads' a template from provided text."""
def __init__(self, text):
self.text = text
def read_other(self, relative):
raise BaseUnavailableError()
"""'Reads' a template from provided text."""
def __init__(self, text):
self.text = text
def read_other(self, relative):
raise BaseUnavailableError()
class EZTException(Exception):
"""Parent class of all EZT exceptions."""
"""Parent class of all EZT exceptions."""
def __init__(self, msg=None, line=None, column=None):
self.msg = msg
self.line = line
self.column = column
def __str__(self):
s = self.__class__.__name__
if self.msg:
s += ' "%s"' % self.msg
if self.line:
s += ' at line %d column %d' % (self.line + 1, self.column + 1)
return s
class ArgCountSyntaxError(EZTException):
"""A bracket directive got the wrong number of arguments."""
"""A bracket directive got the wrong number of arguments."""
class UnknownReference(EZTException):
"""The template references an object not contained in the data dictionary."""
"""The template references an object not contained in the data dictionary."""
class NeedSequenceError(EZTException):
"""The object dereferenced by the template is no sequence (tuple or list)."""
"""The object dereferenced by the template is no sequence (tuple or list)."""
class UnclosedBlocksError(EZTException):
"""This error may be simply a missing [end]."""
"""This error may be simply a missing [end]."""
class UnmatchedEndError(EZTException):
"""This error may be caused by a misspelled if directive."""
"""This error may be caused by a misspelled if directive."""
class UnmatchedElseError(EZTException):
"""This error may be caused by a misspelled if directive."""
class BaseUnavailableError(EZTException):
"""Base location is unavailable, which disables includes."""
"""Base location is unavailable, which disables includes."""
class BadFormatConstantError(EZTException):
"""Format specifiers must be string constants."""
"""Format specifiers must be string constants."""
class UnknownFormatConstantError(EZTException):
"""The format specifier is an unknown value."""
# --- standard test environment ---
def test_parse():
assert _re_parse.split('[a]') == ['', '[a]', None, '']
assert _re_parse.split('[a] [b]') == \
['', '[a]', None, ' ', '[b]', None, '']
assert _re_parse.split('[a c] [b]') == \
['', '[a c]', None, ' ', '[b]', None, '']
assert _re_parse.split('x [a] y [b] z') == \
['x ', '[a]', None, ' y ', '[b]', None, ' z']
assert _re_parse.split('[a "b" c "d"]') == \
['', '[a "b" c "d"]', None, '']
assert _re_parse.split(r'["a \"b[foo]" c.d f]') == \
['', '["a \\"b[foo]" c.d f]', None, '']
def _test(argv):
import doctest, ezt
verbose = "-v" in argv
return doctest.testmod(ezt, verbose=verbose)
if __name__ == "__main__":
# invoke unit test for this module:
import sys
sys.exit(_test(sys.argv)[0])
"""The format specifier is an unknown value."""