You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ws.apache.org by di...@apache.org on 2005/12/09 13:22:06 UTC
svn commit: r355462 [6/7] - in /webservices/admin/planet: ./ cache/
compat_logging/ examples/ fancy-examples/ output/ output/images/
Added: webservices/admin/planet/htmltmpl.py
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/htmltmpl.py?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/htmltmpl.py (added)
+++ webservices/admin/planet/htmltmpl.py Fri Dec 9 04:21:26 2005
@@ -0,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@cpan.org
+
+ @name htmltmpl
+ @version 1.22
+ @author-name Tomas Styblo
+ @author-email tripie@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@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: webservices/admin/planet/htmltmpl.pyc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/htmltmpl.pyc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/htmltmpl.pyc
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/index.html.tmpl
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/index.html.tmpl?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/index.html.tmpl (added)
+++ webservices/admin/planet/index.html.tmpl Fri Dec 9 04:21:26 2005
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="default.css" type="text/css">
+<title><TMPL_VAR name></title>
+</head>
+
+<body>
+<h1><TMPL_VAR name></h1>
+
+<div id="sidebar" float="right">
+
+<h2>Disclaimer</h2>
+<p> The postings on this site are the viewpoints of their authors and don't necessarily represent WSO2's positions, strategies or opinions.</p>
+<h2>Subscriptions</h2>
+<ul>
+<TMPL_LOOP Channels>
+<li><a href="<TMPL_VAR link>" title="<TMPL_VAR title>"><TMPL_VAR name></a> <a href="<TMPL_VAR uri>">(feed)</a></li>
+</TMPL_LOOP>
+</ul>
+
+<h2>Feeds</h2>
+
+<p>Planet WSO2 provides its aggregated feeds in <a href="http://wso2.com/blogs/rss20.xml">RSS 2.0</a>,<a href="http://wso2.com/blogs/rss10.xml">RSS 1.0</a> and <a href="http://wso2.com/blogs/rss09.xml">RSS 0.9</a>, and its blogroll in <a href="http://wso2.com/blogs/foafroll.xml">FOAF</a> and <a href="http://wso2.com/blogs/opml.xml">OPML</a> (the most horrific abuse of XML known to man).</p>
+
+<h2><a href="http://www.planetplanet.org/">Planetarium</a></h2>
+
+<p>There is life on other planets. A heck of a lot, considering the community of Planet sites forming. It's the Big Bang all over again!</p>
+
+ <ul>
+ <li><a href="http://advogato.org/recentlog.html?thresh=4">Advogato</a>
+ <a href="http://advogato.org/">(main)</a></li>
+ <li><a href="http://planet.arslinux.com/">Ars Linux</a></li>
+ <li><a href="http://classpath.wildebeest.org/planet/">ClassPath</a></li>
+ <li><a href="http://planet.debian.net/">Debian</a></li>
+ <li><a href="http://fedora.linux.duke.edu/fedorapeople/">Fedora People</a></li>
+ <li><a href="http://fossplanet.osdir.com/">FOSS</a></li>
+ <li><a href="http://planet.freedesktop.org/">FreeDesktop</a></li>
+ <li><a href="http://planet.gnome.org/">GNOME</a></li>
+ <li><a href="http://gnome.or.kr/pgk/">GNOME Korea</a></li>
+ <li><a href="http://gstreamer.freedesktop.org/planet/">Planet GStreamer</a></li>
+ <li><a href="http://planetjava.org/">Java</a></li>
+ <li><a href="http://www.kdedevelopers.org/blog">KDE Developers</a></li>
+ <li><a href="http://kerneltrap.org/hackers/linux">Linux @ KernelTrap</a></li>
+ <li><a href="http://live.linuxchix.org/">LinuxChix</a></li>
+ <li><a href="http://xach.com/planet-lisp/">Lisp</a></li>
+ <li><a href="http://www.go-mono.com/monologue/">Mono</a></li>
+ <li><a href="http://planet.mozilla.org/">Mozilla</a></li>
+ <li><a href="http://people.novell.com/pn/">Novell</a> (offline atm)</li>
+ <li><a href="http://planet.perl.org/">Perl</a></li>
+ <li><a href="http://www.planet-php.org/">PHP</a></li>
+ <li><a href="http://www.planetpython.org/">Python</a></li>
+ <li><a href="http://www.mechanicalcat.net/pyblagg.html">Python People</a></li>
+ <li><a href="http://planet.rdfhack.com/">RDF</a></li>
+ <li><a href="http://www.planetsuse.org/">SuSE</a></li>
+ <li><a href="http://planetsun.org/">Sun</a></li>
+ <li><a href="http://planet.twistedmatrix.com/">Twisted</a></li>
+ </ul>
+
+</div>
+
+<div id="body">
+
+<TMPL_LOOP Items>
+
+<TMPL_IF new_date>
+<h2><TMPL_VAR new_date></h2>
+</TMPL_IF>
+
+<div class="news">
+<h3><a href="<TMPL_VAR channel_link>"><TMPL_VAR channel_name></a><TMPL_IF title> — <a href="<TMPL_VAR link>"><TMPL_VAR title></a></TMPL_IF></h3>
+<div class="content">
+<TMPL_VAR content>
+<div class="permalink">
+<a href="<TMPL_VAR link>" class="permalink"><TMPL_VAR date></a>
+</div>
+</div>
+</div>
+
+</TMPL_LOOP>
+
+</div>
+
+<div id="footer">
+<p>Updated <TMPL_VAR date></p>
+</div>
+
+</body>
+
+</html>
Added: webservices/admin/planet/index.html.tmplc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/index.html.tmplc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/index.html.tmplc
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/opml.xml.tmpl
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/opml.xml.tmpl?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/opml.xml.tmpl (added)
+++ webservices/admin/planet/opml.xml.tmpl Fri Dec 9 04:21:26 2005
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<opml version="1.1">
+ <head>
+ <title><TMPL_VAR name></title>
+ <dateCreated><TMPL_VAR date></dateCreated>
+ <dateModified><TMPL_VAR date></dateModified>
+ <ownerName>The Apache Community</ownerName>
+ <ownerEmail>community@apache.org</ownerEmail>
+ </head>
+
+ <body>
+ <TMPL_LOOP Channels>
+ <outline text="<TMPL_VAR title ESCAPE="HTML">" xmlUrl="<TMPL_VAR uri ESCAPE="HTML">"/>
+ </TMPL_LOOP>
+ </body>
+</opml>
Added: webservices/admin/planet/opml.xml.tmplc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/opml.xml.tmplc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/opml.xml.tmplc
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/edd.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/edd.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/edd.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/evolution.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/evolution.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/evolution.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/jdub.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/jdub.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/jdub.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/keybuk.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/keybuk.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/keybuk.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/logo.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/logo.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/logo.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/planet.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/planet.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/planet.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/images/thom.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/thom.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.
Propchange: webservices/admin/planet/output/images/thom.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: webservices/admin/planet/output/planet.css
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/planet.css?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/output/planet.css (added)
+++ webservices/admin/planet/output/planet.css Fri Dec 9 04:21:26 2005
@@ -0,0 +1,122 @@
+body {
+ border-right: 1px solid black;
+ margin-right: 200px;
+
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+h1 {
+ margin-top: 0px;
+ padding-top: 20px;
+
+ font-family: "Bitstream Vera Sans", sans-serif;
+ font-weight: normal;
+ letter-spacing: -2px;
+ text-transform: lowercase;
+ text-align: right;
+
+ color: grey;
+}
+
+h2 {
+ font-family: "Bitstream Vera Sans", sans-serif;
+ font-weight: normal;
+ color: #200080;
+
+ margin-left: -20px;
+}
+
+h3 {
+ font-family: "Bitstream Vera Sans", sans-serif;
+ font-weight: normal;
+
+ background-color: #a0c0ff;
+ border: 1px solid #5080b0;
+
+ padding: 4px;
+}
+
+h3 a {
+ text-decoration: none;
+ color: inherit;
+}
+
+h4 {
+ font-family: "Bitstream Vera Sans", sans-serif;
+ font-weight: bold;
+}
+
+h4 a {
+ text-decoration: none;
+ color: inherit;
+}
+
+img.face {
+ float: right;
+ margin-top: -3em;
+}
+
+.entry {
+ margin-bottom: 2em;
+}
+
+.entry .date {
+ font-family: "Bitstream Vera Sans", sans-serif;
+ color: grey;
+}
+
+.entry .date a {
+ text-decoration: none;
+ color: inherit;
+}
+
+.sidebar {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ width: 200px;
+
+ margin-left: 0px;
+ margin-right: 0px;
+ padding-right: 0px;
+
+ padding-top: 20px;
+ padding-left: 0px;
+
+ font-family: "Bitstream Vera Sans", sans-serif;
+ font-size: 85%;
+}
+
+.sidebar h2 {
+ font-size: 110%;
+ font-weight: bold;
+ color: black;
+
+ padding-left: 5px;
+ margin-left: 0px;
+}
+
+.sidebar ul {
+ padding-left: 2em;
+ margin-left: 0px;
+
+ list-style-type: square;
+}
+
+.sidebar ul li:hover {
+ color: grey;
+}
+
+.sidebar p {
+ border-top: 1px solid grey;
+ margin-top: 30px;
+ padding-top: 10px;
+
+ padding-left: 5px;
+}
+
+a:hover {
+ text-decoration: underline !important;
+ color: blue !important;
+}
\ No newline at end of file
Added: webservices/admin/planet/planet.py
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/planet.py?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/planet.py (added)
+++ webservices/admin/planet/planet.py Fri Dec 9 04:21:26 2005
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+"""The Planet RSS Aggregator.
+
+Requires Python 2.2.
+"""
+
+__version__ = "0.2"
+__authors__ = [ "Scott James Remnant <sc...@netsplit.com>",
+ "Jeff Waugh <jd...@perkypants.org>" ]
+__credits__ = "Originally based on spycyroll."
+__license__ = "Python"
+
+
+import sys
+import time
+import os
+
+try:
+ import logging
+except:
+ import compat_logging
+ logging = compat_logging
+
+import planetlib
+stripper = planetlib.Stripper()
+
+from ConfigParser import ConfigParser
+from htmltmpl import TemplateManager, TemplateProcessor
+
+generator = " ".join(("Planet", planetlib.__version__,
+ "http://www.planetplanet.org/"))
+
+
+# Defaults for [Planet] config sections
+CONFIG_FILE = 'config.ini'
+PLANET_NAME = 'Unconfigured Planet'
+PLANET_LINK = 'Unconfigured Planet'
+OWNER_NAME = 'Anonymous Coward'
+OWNER_EMAIL = ''
+TEMPLATE_FILES = " ".join(('examples/planet.html.tmpl',
+ 'examples/planet.rss10.tmpl',
+ 'examples/planet.rss20.tmpl'))
+OUTPUT_DIR = 'output'
+ITEMS_PER_PAGE = 60
+DAYS_PER_PAGE = 0
+DATE_FORMAT = '%B %d, %Y %I:%M %p'
+LOG_LEVEL = 'WARNING'
+
+
+def config_get(config, section, option, default=None, raw=0, vars=None):
+ """Get a value from the configuration, with a default."""
+ if config.has_option(section, option):
+ return config.get(section, option, raw=raw, vars=None)
+ else:
+ return default
+
+def tcfg_get(config, template, option, default=None, raw=0, vars=None):
+ """Get a template value from the configuration, with a default."""
+ if config.has_option(template, option):
+ return config.get(template, option, raw=raw, vars=None)
+ elif config.has_option("Planet", option):
+ return config.get("Planet", option, raw=raw, vars=None)
+ else:
+ return default
+
+
+if __name__ == "__main__":
+ config_file = CONFIG_FILE
+ offline = 0
+
+ for arg in sys.argv[1:]:
+ if arg == '-h' or arg == '--help':
+ print "Usage: planet [options] [CONFIGFILE]"
+ print
+ print "Options:"
+ print " -o, --offline Update the Planet from the cache only"
+ print " -h, --help Display this help message and exit"
+ print
+ sys.exit(0)
+ elif arg == '-o' or arg == '--offline':
+ offline = 1
+ elif arg.startswith("-"):
+ print >>sys.stderr, "Unknown option:", arg
+ sys.exit(1)
+ else:
+ config_file = arg
+
+ config = ConfigParser()
+ config.read(config_file)
+ if not config.has_section('Planet'):
+ print >>sys.stderr, "Configuration missing [Planet] section."
+ sys.exit(1)
+
+ # Read the global configuration values
+ planet_name = config_get(config, 'Planet', 'name', PLANET_NAME)
+ planet_link = config_get(config, 'Planet', 'link', PLANET_LINK)
+ owner_name = config_get(config, 'Planet', 'owner_name', OWNER_NAME)
+ owner_email = config_get(config, 'Planet', 'owner_email', OWNER_EMAIL)
+ if config.has_option('Planet', 'cache_directory'):
+ planetlib.cache_directory = config.get('Planet', 'cache_directory')
+ log_level = config_get(config, 'Planet', 'log_level', LOG_LEVEL)
+ template_files = config_get(config, 'Planet', 'template_files',
+ TEMPLATE_FILES).split(" ")
+
+ # Activate the settings
+ logging.getLogger().setLevel(logging.getLevelName(log_level))
+ planetlib.user_agent = " ".join((planet_name, planet_link,
+ planetlib.user_agent))
+
+ # The other configuration blocks are channels to subscribe to
+ channels = {}
+ planet = planetlib.Planet()
+ for feed in config.sections():
+ if feed == 'Planet' or feed in template_files: continue
+
+ # Build a configuration dict the slow, 2.2-compatible way
+ channel_options = {}
+ options = config.options(feed)
+ for option in options:
+ channel_options[option] = config.get(feed, option)
+
+ # Create the Channel and subscribe to it
+ try:
+ logging.info("Subscribing <" + feed + ">")
+ channel = planetlib.Channel(feed, channel_options)
+ planet.subscribe(channel)
+ except:
+ logging.error("Subscription failure <" + feed + ">",
+ exc_info=1)
+ continue
+
+ try:
+ if not offline:
+ channel.update()
+ except:
+ logging.error("Update from <" + channel.uri + "> failed", exc_info=1)
+
+ # Prepare the template information now
+ info = {}
+ info["uri"] = channel.uri
+ info["title"] = channel.title
+ info["description"] = channel.description
+ info["link"] = channel.link
+ for k, v in channel.props.items():
+ info[k] = v
+ if "name" not in info:
+ info["name"] = channel.title
+
+ channels[channel] = info
+
+ # Sort the channels list by name
+ channel_list = channels.values()
+ channel_list.sort(lambda x,y: cmp(x["name"], y["name"]))
+
+
+ # Go-go-gadget-template
+ for template_file in template_files:
+ output_dir = tcfg_get(config, template_file, 'output_dir', OUTPUT_DIR)
+ date_format = tcfg_get(config, template_file, 'date_format',
+ DATE_FORMAT, raw=1)
+ items_per_page = int(tcfg_get(config, template_file, 'items_per_page',
+ ITEMS_PER_PAGE))
+ days_per_page = int(tcfg_get(config, template_file, 'days_per_page',
+ DAYS_PER_PAGE))
+
+ # We treat each template individually
+ base = os.path.basename(template_file)
+ logging.info("Processing " + base)
+ output_file = os.path.join(output_dir, os.path.splitext(base)[0])
+
+ # Work out the URI to this output file
+ uri = planet_link
+ if not uri.endswith("/"): uri += "/"
+ uri += os.path.basename(output_file)
+
+ # Prepare the template information for the items
+ items = []
+ prev_date = None
+ prev_channel = None
+ date_today_raw = time.time()
+
+ # Prepare information for this template
+ item_count = 0
+ for newsitem in planet.items():
+ format_date = newsitem.channel.format_date
+
+ info = {}
+ info["id"] = newsitem.id
+ info["date"] = format_date(newsitem.date, date_format)
+ info["date_iso"] = format_date(newsitem.date, 'iso')
+ info["date_822"] = format_date(newsitem.date, 'rfc822')
+ info["title"] = newsitem.title
+ info["summary"] = newsitem.summary
+ info["content"] = newsitem.content
+ info["link"] = newsitem.link
+ info["creator"] = newsitem.creator
+
+ chaninfo = channels[newsitem.channel]
+ for k, v in chaninfo.items():
+ info["channel_" + k] = v
+
+ if "channel_keyword" in info:
+ term = info["channel_keyword"]
+ def ifind(needle, haystack):
+ return haystack.lower().find(needle.lower())
+
+ if ifind(term, newsitem.title) == -1 \
+ and ifind(term, stripper.strip(newsitem.summary)) == -1 \
+ and ifind(term, stripper.strip(newsitem.content)) == -1:
+ continue
+
+ date_raw = newsitem.channel.utctime(newsitem.date)
+ date = time.gmtime(date_raw)
+
+ if prev_date != date[:3]:
+ if days_per_page and date_today_raw > date_raw:
+ days_passed = time.gmtime(date_today_raw - date_raw).tm_yday
+ if days_passed >= days_per_page:
+ continue
+
+ info["new_date"] = time.strftime("%B %d, %Y", date)
+ prev_date = date[:3]
+ else:
+ info["new_date"] = ""
+
+ if prev_channel != info["channel_uri"] or info["new_date"] != "":
+ info["new_channel"] = info["channel_uri"]
+ prev_channel = info["channel_uri"]
+ else:
+ info["new_channel"] = ""
+
+ items.append(info)
+
+ # track the number of items added manually, because we skip some
+ item_count += 1
+ if item_count >= items_per_page:
+ break
+
+ # Process the template
+ template = TemplateManager().prepare(template_file)
+ tp = TemplateProcessor(html_escape=0)
+ tp.set("generator", generator)
+ tp.set("name", planet_name)
+ tp.set("link", planet_link)
+ tp.set("owner_name", owner_name)
+ tp.set("owner_email", owner_email)
+ tp.set("uri", uri)
+ tp.set("date", time.strftime(date_format, time.gmtime()))
+ tp.set("date_iso", time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()))
+ tp.set("date_822", time.strftime("%a, %d %b %Y %H:%M:%S +0000",
+ time.gmtime()))
+ tp.set("Items", items)
+ tp.set("Channels", channel_list)
+ try:
+ logging.info("Writing " + output_file)
+ output = open(output_file, "w")
+ output.write(tp.process(template))
+ output.close()
+ except:
+ logging.error("Couldn't save " + output_file, exc_info=1)