[Python-modules-commits] r795 - in /packages/python-htmltmpl: ./
branches/ branches/upstream/
branches/upstream/current/ branches/upstream/current/htmltmpl.py
branches/upstream/current/setup.py tags/
sto at users.alioth.debian.org
sto at users.alioth.debian.org
Wed Jun 14 09:53:43 UTC 2006
Author: sto
Date: Wed Jun 14 09:53:39 2006
New Revision: 795
URL: http://svn.debian.org/wsvn/python-modules/?sc=1&rev=795
Log:
[svn-inject] Installing original source of python-htmltmpl
Added:
packages/python-htmltmpl/
packages/python-htmltmpl/branches/
packages/python-htmltmpl/branches/upstream/
packages/python-htmltmpl/branches/upstream/current/
packages/python-htmltmpl/branches/upstream/current/htmltmpl.py
packages/python-htmltmpl/branches/upstream/current/setup.py (with props)
packages/python-htmltmpl/tags/
Added: packages/python-htmltmpl/branches/upstream/current/htmltmpl.py
URL: http://svn.debian.org/wsvn/python-modules/packages/python-htmltmpl/branches/upstream/current/htmltmpl.py?rev=795&op=file
==============================================================================
--- packages/python-htmltmpl/branches/upstream/current/htmltmpl.py (added)
+++ packages/python-htmltmpl/branches/upstream/current/htmltmpl.py Wed Jun 14 09:53:39 2006
@@ -1,0 +1,1480 @@
+
+""" A templating engine for separation of code and HTML.
+
+ The documentation of this templating engine is separated to two parts:
+
+ 1. Description of the templating language.
+
+ 2. Documentation of classes and API of this module that provides
+ a Python implementation of the templating language.
+
+ All the documentation can be found in 'doc' directory of the
+ distribution tarball or at the homepage of the engine.
+ Latest versions of this module are also available at that website.
+
+ You can use and redistribute this module under conditions of the
+ GNU General Public License that can be found either at
+ [ http://www.gnu.org/ ] or in file "LICENSE" contained in the
+ distribution tarball of this module.
+
+ Copyright (c) 2001 Tomas Styblo, tripie at cpan.org
+
+ @name htmltmpl
+ @version 1.22
+ @author-name Tomas Styblo
+ @author-email tripie at cpan.org
+ @website http://htmltmpl.sourceforge.net/
+ @license-name GNU GPL
+ @license-url http://www.gnu.org/licenses/gpl.html
+"""
+
+__version__ = 1.22
+__author__ = "Tomas Styblo (tripie at cpan.org)"
+
+# All imported modules are part of the standard Python library.
+
+from types import *
+import re
+import os
+import os.path
+import pprint # only for debugging
+import sys
+import copy
+import cgi # for HTML escaping of variables
+import urllib # for URL escaping of variables
+import cPickle # for template compilation
+import gettext
+
+INCLUDE_DIR = "inc"
+
+# Total number of possible parameters.
+# Increment if adding a parameter to any statement.
+PARAMS_NUMBER = 3
+
+# Relative positions of parameters in TemplateCompiler.tokenize().
+PARAM_NAME = 1
+PARAM_ESCAPE = 2
+PARAM_GLOBAL = 3
+PARAM_GETTEXT_STRING = 1
+
+# Find a way to lock files. Currently implemented only for UNIX and windows.
+LOCKTYPE_FCNTL = 1
+LOCKTYPE_MSVCRT = 2
+LOCKTYPE = None
+try:
+ import fcntl
+except:
+ try:
+ import msvcrt
+ except:
+ LOCKTYPE = None
+ else:
+ LOCKTYPE = LOCKTYPE_MSVCRT
+else:
+ LOCKTYPE = LOCKTYPE_FCNTL
+LOCK_EX = 1
+LOCK_SH = 2
+LOCK_UN = 3
+
+##############################################
+# CLASS: TemplateManager #
+##############################################
+
+class TemplateManager:
+ """ Class that manages compilation and precompilation of templates.
+
+ You should use this class whenever you work with templates
+ that are stored in a file. The class can create a compiled
+ template and transparently manage its precompilation. It also
+ keeps the precompiled templates up-to-date by modification times
+ comparisons.
+ """
+
+ def __init__(self, include=1, max_include=5, precompile=1, comments=1,
+ gettext=0, debug=0):
+ """ Constructor.
+
+ @header
+ __init__(include=1, max_include=5, precompile=1, comments=1,
+ gettext=0, debug=0)
+
+ @param include Enable or disable included templates.
+ This optional parameter can be used to enable or disable
+ <em>TMPL_INCLUDE</em> inclusion of templates. Disabling of
+ inclusion can improve performance a bit. The inclusion is
+ enabled by default.
+
+ @param max_include Maximum depth of nested inclusions.
+ This optional parameter can be used to specify maximum depth of
+ nested <em>TMPL_INCLUDE</em> inclusions. It defaults to 5.
+ This setting prevents infinite recursive inclusions.
+
+ @param precompile Enable or disable precompilation of templates.
+ This optional parameter can be used to enable or disable
+ creation and usage of precompiled templates.
+
+ A precompiled template is saved to the same directory in
+ which the main template file is located. You need write
+ permissions to that directory.
+
+ Precompilation provides a significant performance boost because
+ it's not necessary to parse the templates over and over again.
+ The boost is especially noticeable when templates that include
+ other templates are used.
+
+ Comparison of modification times of the main template and all
+ included templates is used to ensure that the precompiled
+ templates are up-to-date. Templates are also recompiled if the
+ htmltmpl module is updated.
+
+ The <em>TemplateError</em>exception is raised when the precompiled
+ template cannot be saved. Precompilation is enabled by default.
+
+ Precompilation is available only on UNIX and Windows platforms,
+ because proper file locking which is necessary to ensure
+ multitask safe behaviour is platform specific and is not
+ implemented for other platforms. Attempts to enable precompilation
+ on the other platforms result in raise of the
+ <em>TemplateError</em> exception.
+
+ @param comments Enable or disable template comments.
+ This optional parameter can be used to enable or disable
+ template comments.
+ Disabling of the comments can improve performance a bit.
+ Comments are enabled by default.
+
+ @param gettext Enable or disable gettext support.
+
+ @param debug Enable or disable debugging messages.
+ This optional parameter is a flag that can be used to enable
+ or disable debugging messages which are printed to the standard
+ error output. The debugging messages are disabled by default.
+ """
+ # Save the optional parameters.
+ # These values are not modified by any method.
+ self._include = include
+ self._max_include = max_include
+ self._precompile = precompile
+ self._comments = comments
+ self._gettext = gettext
+ self._debug = debug
+
+ # Find what module to use to lock files.
+ # File locking is necessary for the 'precompile' feature to be
+ # multitask/thread safe. Currently it works only on UNIX
+ # and Windows. Anyone willing to implement it on Mac ?
+ if precompile and not LOCKTYPE:
+ raise TemplateError, "Template precompilation is not "\
+ "available on this platform."
+ self.DEB("INIT DONE")
+
+ def prepare(self, file):
+ """ Preprocess, parse, tokenize and compile the template.
+
+ If precompilation is enabled then this method tries to load
+ a precompiled form of the template from the same directory
+ in which the template source file is located. If it succeeds,
+ then it compares modification times stored in the precompiled
+ form to modification times of source files of the template,
+ including source files of all templates included via the
+ <em>TMPL_INCLUDE</em> statements. If any of the modification times
+ differs, then the template is recompiled and the precompiled
+ form updated.
+
+ If precompilation is disabled, then this method parses and
+ compiles the template.
+
+ @header prepare(file)
+
+ @return Compiled template.
+ The methods returns an instance of the <em>Template</em> class
+ which is a compiled form of the template. This instance can be
+ used as input for the <em>TemplateProcessor</em>.
+
+ @param file Path to the template file to prepare.
+ The method looks for the template file in current directory
+ if the parameter is a relative path. All included templates must
+ be placed in subdirectory <strong>'inc'</strong> of the
+ directory in which the main template file is located.
+ """
+ compiled = None
+ if self._precompile:
+ if self.is_precompiled(file):
+ try:
+ precompiled = self.load_precompiled(file)
+ except PrecompiledError, template:
+ print >> sys.stderr, "Htmltmpl: bad precompiled "\
+ "template '%s' removed" % template
+ compiled = self.compile(file)
+ self.save_precompiled(compiled)
+ else:
+ precompiled.debug(self._debug)
+ compile_params = (self._include, self._max_include,
+ self._comments, self._gettext)
+ if precompiled.is_uptodate(compile_params):
+ self.DEB("PRECOMPILED: UPTODATE")
+ compiled = precompiled
+ else:
+ self.DEB("PRECOMPILED: NOT UPTODATE")
+ compiled = self.update(precompiled)
+ else:
+ self.DEB("PRECOMPILED: NOT PRECOMPILED")
+ compiled = self.compile(file)
+ self.save_precompiled(compiled)
+ else:
+ self.DEB("PRECOMPILATION DISABLED")
+ compiled = self.compile(file)
+ return compiled
+
+ def update(self, template):
+ """ Update (recompile) a compiled template.
+
+ This method recompiles a template compiled from a file.
+ If precompilation is enabled then the precompiled form saved on
+ disk is also updated.
+
+ @header update(template)
+
+ @return Recompiled template.
+ It's ensured that the returned template is up-to-date.
+
+ @param template A compiled template.
+ This parameter should be an instance of the <em>Template</em>
+ class, created either by the <em>TemplateManager</em> or by the
+ <em>TemplateCompiler</em>. The instance must represent a template
+ compiled from a file on disk.
+ """
+ self.DEB("UPDATE")
+ updated = self.compile(template.file())
+ if self._precompile:
+ self.save_precompiled(updated)
+ return updated
+
+ ##############################################
+ # PRIVATE METHODS #
+ ##############################################
+
+ def DEB(self, str):
+ """ Print debugging message to stderr if debugging is enabled.
+ @hidden
+ """
+ if self._debug: print >> sys.stderr, str
+
+ def lock_file(self, file, lock):
+ """ Provide platform independent file locking.
+ @hidden
+ """
+ fd = file.fileno()
+ if LOCKTYPE == LOCKTYPE_FCNTL:
+ if lock == LOCK_SH:
+ fcntl.flock(fd, fcntl.LOCK_SH)
+ elif lock == LOCK_EX:
+ fcntl.flock(fd, fcntl.LOCK_EX)
+ elif lock == LOCK_UN:
+ fcntl.flock(fd, fcntl.LOCK_UN)
+ else:
+ raise TemplateError, "BUG: bad lock in lock_file"
+ elif LOCKTYPE == LOCKTYPE_MSVCRT:
+ if lock == LOCK_SH:
+ # msvcrt does not support shared locks :-(
+ msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
+ elif lock == LOCK_EX:
+ msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
+ elif lock == LOCK_UN:
+ msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
+ else:
+ raise TemplateError, "BUG: bad lock in lock_file"
+ else:
+ raise TemplateError, "BUG: bad locktype in lock_file"
+
+ def compile(self, file):
+ """ Compile the template.
+ @hidden
+ """
+ return TemplateCompiler(self._include, self._max_include,
+ self._comments, self._gettext,
+ self._debug).compile(file)
+
+ def is_precompiled(self, file):
+ """ Return true if the template is already precompiled on the disk.
+ This method doesn't check whether the compiled template is
+ uptodate.
+ @hidden
+ """
+ filename = file + "c" # "template.tmplc"
+ if os.path.isfile(filename):
+ return 1
+ else:
+ return 0
+
+ def load_precompiled(self, file):
+ """ Load precompiled template from disk.
+
+ Remove the precompiled template file and recompile it
+ if the file contains corrupted or unpicklable data.
+
+ @hidden
+ """
+ filename = file + "c" # "template.tmplc"
+ self.DEB("LOADING PRECOMPILED")
+ try:
+ remove_bad = 0
+ file = None
+ try:
+ file = open(filename, "rb")
+ self.lock_file(file, LOCK_SH)
+ precompiled = cPickle.load(file)
+ except IOError, (errno, errstr):
+ raise TemplateError, "IO error in load precompiled "\
+ "template '%s': (%d) %s"\
+ % (filename, errno, errstr)
+ except cPickle.UnpicklingError:
+ remove_bad = 1
+ raise PrecompiledError, filename
+ except:
+ remove_bad = 1
+ raise
+ else:
+ return precompiled
+ finally:
+ if file:
+ self.lock_file(file, LOCK_UN)
+ file.close()
+ if remove_bad and os.path.isfile(filename):
+ # X: We may lose the original exception here, raising OSError.
+ os.remove(filename)
+
+ def save_precompiled(self, template):
+ """ Save compiled template to disk in precompiled form.
+
+ Associated metadata is also saved. It includes: filename of the
+ main template file, modification time of the main template file,
+ modification times of all included templates and version of the
+ htmltmpl module which compiled the template.
+
+ The method removes a file which is saved only partially because
+ of some error.
+
+ @hidden
+ """
+ filename = template.file() + "c" # creates "template.tmplc"
+ # Check if we have write permission to the template's directory.
+ template_dir = os.path.dirname(os.path.abspath(filename))
+ if not os.access(template_dir, os.W_OK):
+ raise TemplateError, "Cannot save precompiled templates "\
+ "to '%s': write permission denied."\
+ % template_dir
+ try:
+ remove_bad = 0
+ file = None
+ try:
+ file = open(filename, "wb") # may truncate existing file
+ self.lock_file(file, LOCK_EX)
+ BINARY = 1
+ READABLE = 0
+ if self._debug:
+ cPickle.dump(template, file, READABLE)
+ else:
+ cPickle.dump(template, file, BINARY)
+ except IOError, (errno, errstr):
+ remove_bad = 1
+ raise TemplateError, "IO error while saving precompiled "\
+ "template '%s': (%d) %s"\
+ % (filename, errno, errstr)
+ except cPickle.PicklingError, error:
+ remove_bad = 1
+ raise TemplateError, "Pickling error while saving "\
+ "precompiled template '%s': %s"\
+ % (filename, error)
+ except:
+ remove_bad = 1
+ raise
+ else:
+ self.DEB("SAVING PRECOMPILED")
+ finally:
+ if file:
+ self.lock_file(file, LOCK_UN)
+ file.close()
+ if remove_bad and os.path.isfile(filename):
+ # X: We may lose the original exception here, raising OSError.
+ os.remove(filename)
+
+
+##############################################
+# CLASS: TemplateProcessor #
+##############################################
+
+class TemplateProcessor:
+ """ Fill the template with data and process it.
+
+ This class provides actual processing of a compiled template.
+ Use it to set template variables and loops and then obtain
+ result of the processing.
+ """
+
+ def __init__(self, html_escape=1, magic_vars=1, global_vars=0, debug=0):
+ """ Constructor.
+
+ @header __init__(html_escape=1, magic_vars=1, global_vars=0,
+ debug=0)
+
+ @param html_escape Enable or disable HTML escaping of variables.
+ This optional parameter is a flag that can be used to enable or
+ disable automatic HTML escaping of variables.
+ All variables are by default automatically HTML escaped.
+ The escaping process substitutes HTML brackets, ampersands and
+ double quotes with appropriate HTML entities.
+
+ @param magic_vars Enable or disable loop magic variables.
+ This parameter can be used to enable or disable
+ "magic" context variables, that are automatically defined inside
+ loops. Magic variables are enabled by default.
+
+ Refer to the language specification for description of these
+ magic variables.
+
+ @param global_vars Globally activate global lookup of variables.
+ This optional parameter is a flag that can be used to specify
+ whether variables which cannot be found in the current scope
+ should be automatically looked up in enclosing scopes.
+
+ Automatic global lookup is disabled by default. Global lookup
+ can be overriden on a per-variable basis by the
+ <strong>GLOBAL</strong> parameter of a <strong>TMPL_VAR</strong>
+ statement.
+
+ @param debug Enable or disable debugging messages.
+ """
+ self._html_escape = html_escape
+ self._magic_vars = magic_vars
+ self._global_vars = global_vars
+ self._debug = debug
+
+ # Data structure containing variables and loops set by the
+ # application. Use debug=1, process some template and
+ # then check stderr to see how the structure looks.
+ # It's modified only by set() and reset() methods.
+ self._vars = {}
+
+ # Following variables are for multipart templates.
+ self._current_part = 1
+ self._current_pos = 0
+
+ def set(self, var, value):
+ """ Associate a value with top-level template variable or loop.
+
+ A template identifier can represent either an ordinary variable
+ (string) or a loop.
+
+ To assign a value to a string identifier pass a scalar
+ as the 'value' parameter. This scalar will be automatically
+ converted to string.
+
+ To assign a value to a loop identifier pass a list of mappings as
+ the 'value' parameter. The engine iterates over this list and
+ assigns values from the mappings to variables in a template loop
+ block if a key in the mapping corresponds to a name of a variable
+ in the loop block. The number of mappings contained in this list
+ is equal to number of times the loop block is repeated in the
+ output.
+
+ @header set(var, value)
+ @return No return value.
+
+ @param var Name of template variable or loop.
+ @param value The value to associate.
+
+ """
+ # The correctness of character case is verified only for top-level
+ # variables.
+ if self.is_ordinary_var(value):
+ # template top-level ordinary variable
+ if not var.islower():
+ raise TemplateError, "Invalid variable name '%s'." % var
+ elif type(value) == ListType:
+ # template top-level loop
+ if var != var.capitalize():
+ raise TemplateError, "Invalid loop name '%s'." % var
+ else:
+ raise TemplateError, "Value of toplevel variable '%s' must "\
+ "be either a scalar or a list." % var
+ self._vars[var] = value
+ self.DEB("VALUE SET: " + str(var))
+
+ def reset(self, keep_data=0):
+ """ Reset the template data.
+
+ This method resets the data contained in the template processor
+ instance. The template processor instance can be used to process
+ any number of templates, but this method must be called after
+ a template is processed to reuse the instance,
+
+ @header reset(keep_data=0)
+ @return No return value.
+
+ @param keep_data Do not reset the template data.
+ Use this flag if you do not want the template data to be erased.
+ This way you can reuse the data contained in the instance of
+ the <em>TemplateProcessor</em>.
+ """
+ self._current_part = 1
+ self._current_pos = 0
+ if not keep_data:
+ self._vars.clear()
+ self.DEB("RESET")
+
+ def process(self, template, part=None):
+ """ Process a compiled template. Return the result as string.
+
+ This method actually processes a template and returns
+ the result.
+
+ @header process(template, part=None)
+ @return Result of the processing as string.
+
+ @param template A compiled template.
+ Value of this parameter must be an instance of the
+ <em>Template</em> class created either by the
+ <em>TemplateManager</em> or by the <em>TemplateCompiler</em>.
+
+ @param part The part of a multipart template to process.
+ This parameter can be used only together with a multipart
+ template. It specifies the number of the part to process.
+ It must be greater than zero, because the parts are numbered
+ from one.
+
+ The parts must be processed in the right order. You
+ cannot process a part which precedes an already processed part.
+
+ If this parameter is not specified, then the whole template
+ is processed, or all remaining parts are processed.
+ """
+ self.DEB("APP INPUT:")
+ if self._debug: pprint.pprint(self._vars, sys.stderr)
+ if part != None and (part == 0 or part < self._current_part):
+ raise TemplateError, "process() - invalid part number"
+
+ # This flag means "jump behind the end of current statement" or
+ # "skip the parameters of current statement".
+ # Even parameters that actually are not present in the template
+ # do appear in the list of tokens as empty items !
+ skip_params = 0
+
+ # Stack for enabling or disabling output in response to TMPL_IF,
+ # TMPL_UNLESS, TMPL_ELSE and TMPL_LOOPs with no passes.
+ output_control = []
+ ENABLE_OUTPUT = 1
+ DISABLE_OUTPUT = 0
+
+ # Stacks for data related to loops.
+ loop_name = [] # name of a loop
+ loop_pass = [] # current pass of a loop (counted from zero)
+ loop_start = [] # index of loop start in token list
+ loop_total = [] # total number of passes in a loop
+
+ tokens = template.tokens()
+ len_tokens = len(tokens)
+ out = "" # buffer for processed output
+
+ # Recover position at which we ended after processing of last part.
+ i = self._current_pos
+
+ # Process the list of tokens.
+ while 1:
+ if i == len_tokens: break
+ if skip_params:
+ # Skip the parameters following a statement.
+ skip_params = 0
+ i += PARAMS_NUMBER
+ continue
+
+ token = tokens[i]
+ if token.startswith("<TMPL_") or \
+ token.startswith("</TMPL_"):
+ if token == "<TMPL_VAR":
+ # TMPL_VARs should be first. They are the most common.
+ var = tokens[i + PARAM_NAME]
+ if not var:
+ raise TemplateError, "No identifier in <TMPL_VAR>."
+ escape = tokens[i + PARAM_ESCAPE]
+ globalp = tokens[i + PARAM_GLOBAL]
+ skip_params = 1
+
+ # If output of current block is not disabled then append
+ # the substitued and escaped variable to the output.
+ if DISABLE_OUTPUT not in output_control:
+ value = str(self.find_value(var, loop_name, loop_pass,
+ loop_total, globalp))
+ out += self.escape(value, escape)
+ self.DEB("VAR: " + str(var))
+
+ elif token == "<TMPL_LOOP":
+ var = tokens[i + PARAM_NAME]
+ if not var:
+ raise TemplateError, "No identifier in <TMPL_LOOP>."
+ skip_params = 1
+
+ # Find total number of passes in this loop.
+ passtotal = self.find_value(var, loop_name, loop_pass,
+ loop_total)
+ if not passtotal: passtotal = 0
+ # Push data for this loop on the stack.
+ loop_total.append(passtotal)
+ loop_start.append(i)
+ loop_pass.append(0)
+ loop_name.append(var)
+
+ # Disable output of loop block if the number of passes
+ # in this loop is zero.
+ if passtotal == 0:
+ # This loop is empty.
+ output_control.append(DISABLE_OUTPUT)
+ self.DEB("LOOP: DISABLE: " + str(var))
+ else:
+ output_control.append(ENABLE_OUTPUT)
+ self.DEB("LOOP: FIRST PASS: %s TOTAL: %d"\
+ % (var, passtotal))
+
+ elif token == "<TMPL_IF":
+ var = tokens[i + PARAM_NAME]
+ if not var:
+ raise TemplateError, "No identifier in <TMPL_IF>."
+ globalp = tokens[i + PARAM_GLOBAL]
+ skip_params = 1
+ if self.find_value(var, loop_name, loop_pass,
+ loop_total, globalp):
+ output_control.append(ENABLE_OUTPUT)
+ self.DEB("IF: ENABLE: " + str(var))
+ else:
+ output_control.append(DISABLE_OUTPUT)
+ self.DEB("IF: DISABLE: " + str(var))
+
+ elif token == "<TMPL_UNLESS":
+ var = tokens[i + PARAM_NAME]
+ if not var:
+ raise TemplateError, "No identifier in <TMPL_UNLESS>."
+ globalp = tokens[i + PARAM_GLOBAL]
+ skip_params = 1
+ if self.find_value(var, loop_name, loop_pass,
+ loop_total, globalp):
+ output_control.append(DISABLE_OUTPUT)
+ self.DEB("UNLESS: DISABLE: " + str(var))
+ else:
+ output_control.append(ENABLE_OUTPUT)
+ self.DEB("UNLESS: ENABLE: " + str(var))
+
+ elif token == "</TMPL_LOOP":
+ skip_params = 1
+ if not loop_name:
+ raise TemplateError, "Unmatched </TMPL_LOOP>."
+
+ # If this loop was not disabled, then record the pass.
+ if loop_total[-1] > 0: loop_pass[-1] += 1
+
+ if loop_pass[-1] == loop_total[-1]:
+ # There are no more passes in this loop. Pop
+ # the loop from stack.
+ loop_pass.pop()
+ loop_name.pop()
+ loop_start.pop()
+ loop_total.pop()
+ output_control.pop()
+ self.DEB("LOOP: END")
+ else:
+ # Jump to the beggining of this loop block
+ # to process next pass of the loop.
+ i = loop_start[-1]
+ self.DEB("LOOP: NEXT PASS")
+
+ elif token == "</TMPL_IF":
+ skip_params = 1
+ if not output_control:
+ raise TemplateError, "Unmatched </TMPL_IF>."
+ output_control.pop()
+ self.DEB("IF: END")
+
+ elif token == "</TMPL_UNLESS":
+ skip_params = 1
+ if not output_control:
+ raise TemplateError, "Unmatched </TMPL_UNLESS>."
+ output_control.pop()
+ self.DEB("UNLESS: END")
+
+ elif token == "<TMPL_ELSE":
+ skip_params = 1
+ if not output_control:
+ raise TemplateError, "Unmatched <TMPL_ELSE>."
+ if output_control[-1] == DISABLE_OUTPUT:
+ # Condition was false, activate the ELSE block.
+ output_control[-1] = ENABLE_OUTPUT
+ self.DEB("ELSE: ENABLE")
+ elif output_control[-1] == ENABLE_OUTPUT:
+ # Condition was true, deactivate the ELSE block.
+ output_control[-1] = DISABLE_OUTPUT
+ self.DEB("ELSE: DISABLE")
+ else:
+ raise TemplateError, "BUG: ELSE: INVALID FLAG"
+
+ elif token == "<TMPL_BOUNDARY":
+ if part and part == self._current_part:
+ self.DEB("BOUNDARY ON")
+ self._current_part += 1
+ self._current_pos = i + 1 + PARAMS_NUMBER
+ break
+ else:
+ skip_params = 1
+ self.DEB("BOUNDARY OFF")
+ self._current_part += 1
+
+ elif token == "<TMPL_INCLUDE":
+ # TMPL_INCLUDE is left in the compiled template only
+ # when it was not replaced by the parser.
+ skip_params = 1
+ filename = tokens[i + PARAM_NAME]
+ out += """
+ <br />
+ <p>
+ <strong>HTMLTMPL WARNING:</strong><br />
+ Cannot include template: <strong>%s</strong>
+ </p>
+ <br />
+ """ % filename
+ self.DEB("CANNOT INCLUDE WARNING")
+
+ elif token == "<TMPL_GETTEXT":
+ skip_params = 1
+ if DISABLE_OUTPUT not in output_control:
+ text = tokens[i + PARAM_GETTEXT_STRING]
+ out += gettext.gettext(text)
+ self.DEB("GETTEXT: " + text)
+
+ else:
+ # Unknown processing directive.
+ raise TemplateError, "Invalid statement %s>." % token
+
+ elif DISABLE_OUTPUT not in output_control:
+ # Raw textual template data.
+ # If output of current block is not disabled, then
+ # append template data to the output buffer.
+ out += token
+
+ i += 1
+ # end of the big while loop
+
+ # Check whether all opening statements were closed.
+ if loop_name: raise TemplateError, "Missing </TMPL_LOOP>."
+ if output_control: raise TemplateError, "Missing </TMPL_IF> or </TMPL_UNLESS>"
+ return out
+
+ ##############################################
+ # PRIVATE METHODS #
+ ##############################################
+
+ def DEB(self, str):
+ """ Print debugging message to stderr if debugging is enabled.
+ @hidden
+ """
+ if self._debug: print >> sys.stderr, str
+
+ def find_value(self, var, loop_name, loop_pass, loop_total,
+ global_override=None):
+ """ Search the self._vars data structure to find variable var
+ located in currently processed pass of a loop which
+ is currently being processed. If the variable is an ordinary
+ variable, then return it.
+
+ If the variable is an identificator of a loop, then
+ return the total number of times this loop will
+ be executed.
+
+ Return an empty string, if the variable is not
+ found at all.
+
+ @hidden
+ """
+ # Search for the requested variable in magic vars if the name
+ # of the variable starts with "__" and if we are inside a loop.
+ if self._magic_vars and var.startswith("__") and loop_name:
+ return self.magic_var(var, loop_pass[-1], loop_total[-1])
+
+ # Search for an ordinary variable or for a loop.
+ # Recursively search in self._vars for the requested variable.
+ scope = self._vars
+ globals = []
+ for i in range(len(loop_name)):
+ # If global lookup is on then push the value on the stack.
+ if ((self._global_vars and global_override != "0") or \
+ global_override == "1") and scope.has_key(var) and \
+ self.is_ordinary_var(scope[var]):
+ globals.append(scope[var])
+
+ # Descent deeper into the hierarchy.
+ if scope.has_key(loop_name[i]) and scope[loop_name[i]]:
+ scope = scope[loop_name[i]][loop_pass[i]]
+ else:
+ return ""
+
+ if scope.has_key(var):
+ # Value exists in current loop.
+ if type(scope[var]) == ListType:
+ # The requested value is a loop.
+ # Return total number of its passes.
+ return len(scope[var])
+ else:
+ return scope[var]
+ elif globals and \
+ ((self._global_vars and global_override != "0") or \
+ global_override == "1"):
+ # Return globally looked up value.
+ return globals.pop()
+ else:
+ # No value found.
+ if var[0].isupper():
+ # This is a loop name.
+ # Return zero, because the user wants to know number
+ # of its passes.
+ return 0
+ else:
+ return ""
+
+ def magic_var(self, var, loop_pass, loop_total):
+ """ Resolve and return value of a magic variable.
+ Raise an exception if the magic variable is not recognized.
+
+ @hidden
+ """
+ self.DEB("MAGIC: '%s', PASS: %d, TOTAL: %d"\
+ % (var, loop_pass, loop_total))
+ if var == "__FIRST__":
+ if loop_pass == 0:
+ return 1
+ else:
+ return 0
+ elif var == "__LAST__":
+ if loop_pass == loop_total - 1:
+ return 1
+ else:
+ return 0
+ elif var == "__INNER__":
+ # If this is neither the first nor the last pass.
+ if loop_pass != 0 and loop_pass != loop_total - 1:
+ return 1
+ else:
+ return 0
+ elif var == "__PASS__":
+ # Magic variable __PASS__ counts passes from one.
+ return loop_pass + 1
+ elif var == "__PASSTOTAL__":
+ return loop_total
+ elif var == "__ODD__":
+ # Internally pass numbers stored in loop_pass are counted from
+ # zero. But the template language presents them counted from one.
+ # Therefore we must add one to the actual loop_pass value to get
+ # the value we present to the user.
+ if (loop_pass + 1) % 2 != 0:
+ return 1
+ else:
+ return 0
+ elif var.startswith("__EVERY__"):
+ # Magic variable __EVERY__x is never true in first or last pass.
+ if loop_pass != 0 and loop_pass != loop_total - 1:
+ # Check if an integer follows the variable name.
+ try:
+ every = int(var[9:]) # nine is length of "__EVERY__"
+ except ValueError:
+ raise TemplateError, "Magic variable __EVERY__x: "\
+ "Invalid pass number."
+ else:
+ if not every:
+ raise TemplateError, "Magic variable __EVERY__x: "\
+ "Pass number cannot be zero."
+ elif (loop_pass + 1) % every == 0:
+ self.DEB("MAGIC: EVERY: " + str(every))
+ return 1
+ else:
+ return 0
+ else:
+ return 0
+ else:
+ raise TemplateError, "Invalid magic variable '%s'." % var
+
+ def escape(self, str, override=""):
+ """ Escape a string either by HTML escaping or by URL escaping.
+ @hidden
+ """
+ ESCAPE_QUOTES = 1
+ if (self._html_escape and override != "NONE" and override != "0" and \
+ override != "URL") or override == "HTML" or override == "1":
+ return cgi.escape(str, ESCAPE_QUOTES)
+ elif override == "URL":
+ return urllib.quote_plus(str)
+ else:
+ return str
+
+ def is_ordinary_var(self, var):
+ """ Return true if var is a scalar. (not a reference to loop)
+ @hidden
+ """
+ if type(var) == StringType or type(var) == IntType or \
+ type(var) == LongType or type(var) == FloatType:
+ return 1
+ else:
+ return 0
+
+
+##############################################
+# CLASS: TemplateCompiler #
+##############################################
+
+class TemplateCompiler:
+ """ Preprocess, parse, tokenize and compile the template.
+
+ This class parses the template and produces a 'compiled' form
+ of it. This compiled form is an instance of the <em>Template</em>
+ class. The compiled form is used as input for the TemplateProcessor
+ which uses it to actually process the template.
+
+ This class should be used direcly only when you need to compile
+ a template from a string. If your template is in a file, then you
+ should use the <em>TemplateManager</em> class which provides
+ a higher level interface to this class and also can save the
+ compiled template to disk in a precompiled form.
+ """
+
+ def __init__(self, include=1, max_include=5, comments=1, gettext=0,
+ debug=0):
+ """ Constructor.
+
+ @header __init__(include=1, max_include=5, comments=1, gettext=0,
+ debug=0)
+
+ @param include Enable or disable included templates.
+ @param max_include Maximum depth of nested inclusions.
+ @param comments Enable or disable template comments.
+ @param gettext Enable or disable gettext support.
+ @param debug Enable or disable debugging messages.
+ """
+
+ self._include = include
+ self._max_include = max_include
+ self._comments = comments
+ self._gettext = gettext
+ self._debug = debug
+
+ # This is a list of filenames of all included templates.
+ # It's modified by the include_templates() method.
+ self._include_files = []
+
+ # This is a counter of current inclusion depth. It's used to prevent
+ # infinite recursive includes.
+ self._include_level = 0
+
+ def compile(self, file):
+ """ Compile template from a file.
+
+ @header compile(file)
+ @return Compiled template.
+ The return value is an instance of the <em>Template</em>
+ class.
+
+ @param file Filename of the template.
+ See the <em>prepare()</em> method of the <em>TemplateManager</em>
+ class for exaplanation of this parameter.
+ """
+
+ self.DEB("COMPILING FROM FILE: " + file)
+ self._include_path = os.path.join(os.path.dirname(file), INCLUDE_DIR)
+ tokens = self.parse(self.read(file))
+ compile_params = (self._include, self._max_include, self._comments,
+ self._gettext)
+ return Template(__version__, file, self._include_files,
+ tokens, compile_params, self._debug)
+
+ def compile_string(self, data):
+ """ Compile template from a string.
+
+ This method compiles a template from a string. The
+ template cannot include any templates.
+ <strong>TMPL_INCLUDE</strong> statements are turned into warnings.
+
+ @header compile_string(data)
+ @return Compiled template.
+ The return value is an instance of the <em>Template</em>
+ class.
+
+ @param data String containing the template data.
+ """
+ self.DEB("COMPILING FROM STRING")
+ self._include = 0
+ tokens = self.parse(data)
+ compile_params = (self._include, self._max_include, self._comments,
+ self._gettext)
+ return Template(__version__, None, None, tokens, compile_params,
+ self._debug)
+
+ ##############################################
+ # PRIVATE METHODS #
+ ##############################################
+
+ def DEB(self, str):
+ """ Print debugging message to stderr if debugging is enabled.
+ @hidden
+ """
+ if self._debug: print >> sys.stderr, str
+
+ def read(self, filename):
+ """ Read content of file and return it. Raise an error if a problem
+ occurs.
+ @hidden
+ """
+ self.DEB("READING: " + filename)
+ try:
+ f = None
+ try:
+ f = open(filename, "r")
+ data = f.read()
+ except IOError, (errno, errstr):
+ raise TemplateError, "IO error while reading template '%s': "\
+ "(%d) %s" % (filename, errno, errstr)
+ else:
+ return data
+ finally:
+ if f: f.close()
+
+ def parse(self, template_data):
+ """ Parse the template. This method is recursively called from
+ within the include_templates() method.
+
+ @return List of processing tokens.
+ @hidden
+ """
+ if self._comments:
+ self.DEB("PREPROCESS: COMMENTS")
+ template_data = self.remove_comments(template_data)
+ tokens = self.tokenize(template_data)
+ if self._include:
+ self.DEB("PREPROCESS: INCLUDES")
+ self.include_templates(tokens)
+ return tokens
+
+ def remove_comments(self, template_data):
+ """ Remove comments from the template data.
+ @hidden
+ """
+ pattern = r"### .*"
+ return re.sub(pattern, "", template_data)
+
+ def include_templates(self, tokens):
+ """ Process TMPL_INCLUDE statements. Use the include_level counter
+ to prevent infinite recursion. Record paths to all included
+ templates to self._include_files.
+ @hidden
+ """
+ i = 0
+ out = "" # buffer for output
+ skip_params = 0
+
+ # Process the list of tokens.
+ while 1:
+ if i == len(tokens): break
+ if skip_params:
+ skip_params = 0
+ i += PARAMS_NUMBER
+ continue
+
+ token = tokens[i]
+ if token == "<TMPL_INCLUDE":
+ filename = tokens[i + PARAM_NAME]
+ if not filename:
+ raise TemplateError, "No filename in <TMPL_INCLUDE>."
+ self._include_level += 1
+ if self._include_level > self._max_include:
+ # Do not include the template.
+ # Protection against infinite recursive includes.
+ skip_params = 1
+ self.DEB("INCLUDE: LIMIT REACHED: " + filename)
+ else:
+ # Include the template.
+ skip_params = 0
+ include_file = os.path.join(self._include_path, filename)
+ self._include_files.append(include_file)
+ include_data = self.read(include_file)
+ include_tokens = self.parse(include_data)
+
+ # Append the tokens from the included template to actual
+ # position in the tokens list, replacing the TMPL_INCLUDE
+ # token and its parameters.
+ tokens[i:i+PARAMS_NUMBER+1] = include_tokens
+ i = i + len(include_tokens)
+ self.DEB("INCLUDED: " + filename)
+ continue # Do not increment 'i' below.
+ i += 1
+ # end of the main while loop
+
+ if self._include_level > 0: self._include_level -= 1
+ return out
+
+ def tokenize(self, template_data):
+ """ Split the template into tokens separated by template statements.
+ The statements itself and associated parameters are also
+ separately included in the resulting list of tokens.
+ Return list of the tokens.
+
+ @hidden
+ """
+ self.DEB("TOKENIZING TEMPLATE")
+ # NOTE: The TWO double quotes in character class in the regexp below
+ # are there only to prevent confusion of syntax highlighter in Emacs.
+ pattern = r"""
+ (?:^[ \t]+)? # eat spaces, tabs (opt.)
+ (<
+ (?:!--[ ])? # comment start + space (opt.)
+ /?TMPL_[A-Z]+ # closing slash / (opt.) + statement
+ [ a-zA-Z0-9""/.=:_\\-]* # this spans also comments ending (--)
+ >)
+ [%s]? # eat trailing newline (opt.)
+ """ % os.linesep
+ rc = re.compile(pattern, re.VERBOSE | re.MULTILINE)
+ split = rc.split(template_data)
+ tokens = []
+ for statement in split:
+ if statement.startswith("<TMPL_") or \
+ statement.startswith("</TMPL_") or \
+ statement.startswith("<!-- TMPL_") or \
+ statement.startswith("<!-- /TMPL_"):
+ # Processing statement.
+ statement = self.strip_brackets(statement)
+ params = re.split(r"\s+", statement)
+ tokens.append(self.find_directive(params))
+ tokens.append(self.find_name(params))
+ tokens.append(self.find_param("ESCAPE", params))
+ tokens.append(self.find_param("GLOBAL", params))
+ else:
+ # "Normal" template data.
+ if self._gettext:
+ self.DEB("PARSING GETTEXT STRINGS")
+ self.gettext_tokens(tokens, statement)
+ else:
+ tokens.append(statement)
+ return tokens
+
+ def gettext_tokens(self, tokens, str):
+ """ Find gettext strings and return appropriate array of
+ processing tokens.
+ @hidden
+ """
+ escaped = 0
+ gt_mode = 0
+ i = 0
+ buf = ""
+ while(1):
+ if i == len(str): break
+ if str[i] == "\\":
+ escaped = 0
+ if str[i+1] == "\\":
+ buf += "\\"
+ i += 2
+ continue
+ elif str[i+1] == "[" or str[i+1] == "]":
+ escaped = 1
+ else:
+ buf += "\\"
+ elif str[i] == "[" and str[i+1] == "[":
+ if gt_mode:
+ if escaped:
+ escaped = 0
+ buf += "["
+ else:
+ buf += "["
+ else:
+ if escaped:
+ escaped = 0
+ buf += "["
+ else:
+ tokens.append(buf)
+ buf = ""
+ gt_mode = 1
+ i += 2
+ continue
+ elif str[i] == "]" and str[i+1] == "]":
+ if gt_mode:
+ if escaped:
+ escaped = 0
+ buf += "]"
+ else:
+ self.add_gettext_token(tokens, buf)
+ buf = ""
+ gt_mode = 0
+ i += 2
+ continue
+ else:
+ if escaped:
+ escaped = 0
+ buf += "]"
+ else:
+ buf += "]"
+ else:
+ escaped = 0
+ buf += str[i]
+ i += 1
+ # end of the loop
+
+ if buf:
+ tokens.append(buf)
+
+ def add_gettext_token(self, tokens, str):
+ """ Append a gettext token and gettext string to the tokens array.
+ @hidden
+ """
+ self.DEB("GETTEXT PARSER: TOKEN: " + str)
+ tokens.append("<TMPL_GETTEXT")
+ tokens.append(str)
+ tokens.append(None)
+ tokens.append(None)
+
+ def strip_brackets(self, statement):
+ """ Strip HTML brackets (with optional HTML comments) from the
+ beggining and from the end of a statement.
+ @hidden
+ """
+ if statement.startswith("<!-- TMPL_") or \
+ statement.startswith("<!-- /TMPL_"):
+ return statement[5:-4]
+ else:
+ return statement[1:-1]
+
+ def find_directive(self, params):
+ """ Extract processing directive (TMPL_*) from a statement.
+ @hidden
+ """
+ directive = params[0]
+ del params[0]
+ self.DEB("TOKENIZER: DIRECTIVE: " + directive)
+ return "<" + directive
+
+ def find_name(self, params):
+ """ Extract identifier from a statement. The identifier can be
+ specified both implicitely or explicitely as a 'NAME' parameter.
+ @hidden
+ """
+ if len(params) > 0 and '=' not in params[0]:
+ # implicit identifier
+ name = params[0]
+ del params[0]
+ else:
+ # explicit identifier as a 'NAME' parameter
+ name = self.find_param("NAME", params)
+ self.DEB("TOKENIZER: NAME: " + str(name))
+ return name
+
+ def find_param(self, param, params):
+ """ Extract value of parameter from a statement.
+ @hidden
+ """
+ for pair in params:
+ name, value = pair.split("=")
+ if not name or not value:
+ raise TemplateError, "Syntax error in template."
+ if name == param:
+ if value[0] == '"':
+ # The value is in double quotes.
+ ret_value = value[1:-1]
+ else:
+ # The value is without double quotes.
+ ret_value = value
+ self.DEB("TOKENIZER: PARAM: '%s' => '%s'" % (param, ret_value))
+ return ret_value
+ else:
+ self.DEB("TOKENIZER: PARAM: '%s' => NOT DEFINED" % param)
+ return None
+
+
+##############################################
+# CLASS: Template #
+##############################################
+
+class Template:
+ """ This class represents a compiled template.
+
+ This class provides storage and methods for the compiled template
+ and associated metadata. It's serialized by pickle if we need to
+ save the compiled template to disk in a precompiled form.
+
+ You should never instantiate this class directly. Always use the
+ <em>TemplateManager</em> or <em>TemplateCompiler</em> classes to
+ create the instances of this class.
+
+ The only method which you can directly use is the <em>is_uptodate</em>
+ method.
+ """
+
+ def __init__(self, version, file, include_files, tokens, compile_params,
+ debug=0):
+ """ Constructor.
+ @hidden
+ """
+ self._version = version
+ self._file = file
+ self._tokens = tokens
+ self._compile_params = compile_params
+ self._debug = debug
+ self._mtime = None
+ self._include_mtimes = {}
+
+ if not file:
+ self.DEB("TEMPLATE WAS COMPILED FROM A STRING")
+ return
+
+ # Save modifitcation time of the main template file.
+ if os.path.isfile(file):
+ self._mtime = os.path.getmtime(file)
+ else:
+ raise TemplateError, "Template: file does not exist: '%s'" % file
+
+ # Save modificaton times of all included template files.
+ for inc_file in include_files:
+ if os.path.isfile(inc_file):
+ self._include_mtimes[inc_file] = os.path.getmtime(inc_file)
+ else:
+ raise TemplateError, "Template: file does not exist: '%s'"\
+ % inc_file
+
+ self.DEB("NEW TEMPLATE CREATED")
+
+ def is_uptodate(self, compile_params=None):
+ """ Check whether the compiled template is uptodate.
+
+ Return true if this compiled template is uptodate.
+ Return false, if the template source file was changed on the
+ disk since it was compiled.
+ Works by comparison of modification times.
+ Also takes modification times of all included templates
+ into account.
+
+ @header is_uptodate(compile_params=None)
+ @return True if the template is uptodate, false otherwise.
+
+ @param compile_params Only for internal use.
+ Do not use this optional parameter. It's intended only for
+ internal use by the <em>TemplateManager</em>.
+ """
+ if not self._file:
+ self.DEB("TEMPLATE COMPILED FROM A STRING")
+ return 0
+
+ if self._version != __version__:
+ self.DEB("TEMPLATE: VERSION NOT UPTODATE")
+ return 0
+
+ if compile_params != None and compile_params != self._compile_params:
+ self.DEB("TEMPLATE: DIFFERENT COMPILATION PARAMS")
+ return 0
+
+ # Check modification times of the main template and all included
+ # templates. If the included template no longer exists, then
+ # the problem will be resolved when the template is recompiled.
+
+ # Main template file.
+ if not (os.path.isfile(self._file) and \
+ self._mtime == os.path.getmtime(self._file)):
+ self.DEB("TEMPLATE: NOT UPTODATE: " + self._file)
+ return 0
+
+ # Included templates.
+ for inc_file in self._include_mtimes.keys():
+ if not (os.path.isfile(inc_file) and \
+ self._include_mtimes[inc_file] == \
+ os.path.getmtime(inc_file)):
+ self.DEB("TEMPLATE: NOT UPTODATE: " + inc_file)
+ return 0
+ else:
+ self.DEB("TEMPLATE: UPTODATE")
+ return 1
+
+ def tokens(self):
+ """ Get tokens of this template.
+ @hidden
+ """
+ return self._tokens
+
+ def file(self):
+ """ Get filename of the main file of this template.
+ @hidden
+ """
+ return self._file
+
+ def debug(self, debug):
+ """ Get debugging state.
+ @hidden
+ """
+ self._debug = debug
+
+ ##############################################
+ # PRIVATE METHODS #
+ ##############################################
+
+ def __getstate__(self):
+ """ Used by pickle when the class is serialized.
+ Remove the 'debug' attribute before serialization.
+ @hidden
+ """
+ dict = copy.copy(self.__dict__)
+ del dict["_debug"]
+ return dict
+
+ def __setstate__(self, dict):
+ """ Used by pickle when the class is unserialized.
+ Add the 'debug' attribute.
+ @hidden
+ """
+ dict["_debug"] = 0
+ self.__dict__ = dict
+
+
+ def DEB(self, str):
+ """ Print debugging message to stderr.
+ @hidden
+ """
+ if self._debug: print >> sys.stderr, str
+
+
+##############################################
+# EXCEPTIONS #
+##############################################
+
+class TemplateError(Exception):
+ """ Fatal exception. Raised on runtime or template syntax errors.
+
+ This exception is raised when a runtime error occurs or when a syntax
+ error in the template is found. It has one parameter which always
+ is a string containing a description of the error.
+
+ All potential IOError exceptions are handled by the module and are
+ converted to TemplateError exceptions. That means you should catch the
+ TemplateError exception if there is a possibility that for example
+ the template file will not be accesssible.
+
+ The exception can be raised by constructors or by any method of any
+ class.
+
+ The instance is no longer usable when this exception is raised.
+ """
+
+ def __init__(self, error):
+ """ Constructor.
+ @hidden
+ """
+ Exception.__init__(self, "Htmltmpl error: " + error)
+
+
+class PrecompiledError(Exception):
+ """ This exception is _PRIVATE_ and non fatal.
+ @hidden
+ """
+
+ def __init__(self, template):
+ """ Constructor.
+ @hidden
+ """
+ Exception.__init__(self, template)
+
Added: packages/python-htmltmpl/branches/upstream/current/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/python-htmltmpl/branches/upstream/current/setup.py?rev=795&op=file
==============================================================================
--- packages/python-htmltmpl/branches/upstream/current/setup.py (added)
+++ packages/python-htmltmpl/branches/upstream/current/setup.py Wed Jun 14 09:53:39 2006
@@ -1,0 +1,13 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+
+setup(name = "htmltmpl",
+ version = "1.22",
+ description = "Templating engine for separation of code and HTML.",
+ author = "Tomas Styblo",
+ author_email = "tripie at cpan.org",
+ url = "http://htmltmpl.sourceforge.net/",
+ licence = "GNU GPL",
+ py_modules = ['htmltmpl', 'easydoc', 'easydocp']
+ )
Propchange: packages/python-htmltmpl/branches/upstream/current/setup.py
------------------------------------------------------------------------------
svn:executable =
More information about the Python-modules-commits
mailing list