[med-svn] r1258 - in trunk/community: . infrastructure infrastructure/scripts

hanska-guest at alioth.debian.org hanska-guest at alioth.debian.org
Sat Feb 2 12:12:23 UTC 2008


Author: hanska-guest
Date: 2008-02-02 12:12:23 +0000 (Sat, 02 Feb 2008)
New Revision: 1258

Added:
   trunk/community/infrastructure/scripts/
   trunk/community/infrastructure/scripts/Commit.py
   trunk/community/infrastructure/scripts/HTMLTemplate.py
   trunk/community/infrastructure/scripts/NEEDED
   trunk/community/infrastructure/scripts/Tools.py
   trunk/community/infrastructure/scripts/check-static
   trunk/community/infrastructure/scripts/ciabot_svn.py
   trunk/community/infrastructure/scripts/cronfile
   trunk/community/infrastructure/scripts/debianqa.conf
   trunk/community/infrastructure/scripts/make-policy
   trunk/community/infrastructure/scripts/message.xml
   trunk/community/infrastructure/scripts/parse_xml.py
   trunk/community/infrastructure/scripts/qa/
   trunk/community/infrastructure/scripts/update-bugs
   trunk/community/infrastructure/scripts/update-ddtp
   trunk/community/infrastructure/scripts/update-tasks
   trunk/community/infrastructure/scripts/update-tasks-wrapper
   trunk/community/infrastructure/scripts/update-website
Removed:
   trunk/community/infrastructure/LibCIA/
   trunk/community/infrastructure/Nouvelle/
   trunk/community/infrastructure/check-static
   trunk/community/infrastructure/update-bugs
   trunk/community/infrastructure/update-ddtp
   trunk/community/infrastructure/update-tasks
   trunk/community/infrastructure/update-tasks-wrapper
   trunk/community/qa/
Log:
Putting scripts under SVN, let's hope it works
(post-commit hook already edited :)


Deleted: trunk/community/infrastructure/check-static
===================================================================
--- trunk/community/infrastructure/check-static	2008-02-02 11:03:47 UTC (rev 1257)
+++ trunk/community/infrastructure/check-static	2008-02-02 12:12:23 UTC (rev 1258)
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-BASE=/var/lib/gforge/chroot/home/groups/debian-med
-
-cp -rf $BASE/static/* $BASE/htdocs/

Added: trunk/community/infrastructure/scripts/Commit.py
===================================================================
--- trunk/community/infrastructure/scripts/Commit.py	                        (rev 0)
+++ trunk/community/infrastructure/scripts/Commit.py	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,491 @@
+""" LibCIA.Formatters.Commit
+
+Formatters used for converting commit messages to other formats.
+Note that this only handles real XML commit messages. The legacy
+'colorText' messages are handled by a separate module.
+"""
+#
+# CIA open source notification system
+# Copyright (C) 2003-2007 Micah Dowty <micah at navi.cx>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+from LibCIA import Message, XML
+from Nouvelle import tag
+import re, posixpath
+from twisted.python.util import OrderedDict
+from LibCIA.Formatters import Util
+
+__all__ = ['CommitToXHTML', 'CommitToXHTMLLong']
+
+
+class CommitFormatter(Message.ModularFormatter):
+    """Base class for formatters that operate on commit messages.
+       Includes a filter for commit messages, and utilities for
+       extracting useful information from the commits.
+       """
+    filter = '<find path="/message/body/commit"/>'
+    defaultComponentTree = """
+    <format>
+        <author/> <branch/> *
+        <version/><autoHide>r<revision/></autoHide>
+        <module/>/<files/>:
+        <log/>
+    </format>
+    """
+
+    # Subclasses can set this to limit the length of log messages, in lines
+    lineLimit = None
+
+    # Lines in the log longer than this are wrapped to wrapWidth
+    widthLimit = None
+    wrapWidth = None
+
+    # If the list of files ends up longer than this many characters, summarize it
+    filesWidthLimit = 60
+
+    # Instead of using our default pseudo-smart whitespace normalizing algorithm,
+    # we can optionally replace all whitespace with single space characters.
+    crunchWhitespace = False
+
+    def param_crunchWhitespace(self, tag):
+        self.crunchWhitespace = True
+
+    def param_lineLimit(self, tag):
+        self.lineLimit = int(XML.shallowText(tag))
+
+    def param_widthLimit(self, tag):
+        self.widthLimit = int(XML.shallowText(tag))
+        if self.wrapWidth > self.widthLimit:
+            self.wrapWidth = self.widthLimit
+
+    def param_wrapWidth(self, tag):
+        self.wrapWidth = int(XML.shallowText(tag))
+
+    def param_filesWidthLimit(self, tag):
+        self.filesWidthLimit = int(XML.shallowText(tag))
+
+    def component_author(self, element, args):
+        return self.textComponent(element, args, "message", "body", "commit", "author")
+
+    def component_version(self, element, args):
+        return self.textComponent(element, args, "message", "body", "commit", "version")
+
+    def component_revision(self, element, args):
+        return self.textComponent(element, args, "message", "body", "commit", "revision")
+
+    def component_branch(self, element, args):
+        return self.textComponent(element, args, "message", "source", "branch")
+
+    def component_module(self, element, args):
+        return self.textComponent(element, args, "message", "source", "module")
+
+    def component_project(self, element, args):
+        return self.textComponent(element, args, "message", "source", "project")
+
+    def component_files(self, element, args):
+        """Break up our list of files into a common prefix and a sensibly-sized
+           list of filenames after that prefix.
+           """
+        files = XML.dig(args.message.xml, "message", "body", "commit", "files")
+        if not (files and XML.hasChildElements(files)):
+            return [Message.MarkAsHidden()]
+
+        prefix, endings = self.consolidateFiles(files)
+        endingStr = " ".join(endings)
+        if len(endingStr) > self.filesWidthLimit:
+            # If the full file list is too long, give a file summary instead
+            endingStr = self.summarizeFiles(endings)
+        if prefix.startswith('/'):
+            prefix = prefix[1:]
+
+        if endingStr:
+            return ["%s (%s)" % (prefix, endingStr)]
+        else:
+            return [prefix]
+
+    def component_log(self, element, args):
+        log = XML.dig(args.message.xml, "message", "body", "commit", "log")
+        if not log:
+            return [Message.MarkAsHidden()]
+
+        if self.crunchWhitespace:
+            inputLines = [Util.getCrunchedLog(log)]
+        else:
+            inputLines = Util.getNormalizedLog(log)
+
+        # Break the log string into wrapped lines
+        lines = []
+        for line in inputLines:
+            # Ignore blank lines
+            if not line:
+                continue
+
+            # Wrap long lines
+            if self.widthLimit and len(line) > self.widthLimit:
+                lines.extend(Util.wrapLine(line, self.wrapWidth))
+            else:
+                lines.append(line)
+
+        # If our lineLimit is 1, don't bother starting long logs on the
+        # next line since there will be no long logs. Instead of the
+        # long (log message trimmed), just add an ellipsis.
+        if self.lineLimit == 1:
+            if len(lines) > 1:
+                lines[0] += ' ...'
+                del lines[1:]
+
+        # Multiline logs shouldn't start on the same line as the metadata
+        elif len(lines) > 1:
+            lines.insert(0, '')
+
+            # Truncate long log messages if we have a limit
+            if self.lineLimit and len(lines) > self.lineLimit + 1:
+                lines[0] = "(log message trimmed)"
+                del lines[self.lineLimit + 1:]
+
+        # Reassemble the log message and send it to the default formatter
+        return ["\n".join(lines)]
+
+    def summarizeFiles(self, files):
+        """Given a list of strings representing file paths, return
+           a summary of those files and/or directories. This is used
+           in place of a full file list when that would be too long.
+           """
+        # Count the number of distinct directories we have
+        dirs = {}
+        for file in files:
+            dirs[posixpath.split(file)[0]] = True
+
+        if len(dirs) <= 1:
+            return "%d files" % len(files)
+        else:
+            return "%d files in %d dirs" % (len(files), len(dirs))
+
+    def consolidateFiles(self, xmlFiles):
+        """Given a <files> element, find the directory common to all files
+           and return a 2-tuple with that directory followed by
+           a list of files within that directory.
+           """
+        files = []
+        if xmlFiles:
+            for fileTag in XML.getChildElements(xmlFiles):
+                if fileTag.nodeName == 'file':
+                    files.append(XML.shallowText(fileTag))
+
+        # If we only have one file, return it as the prefix.
+        # This prevents the below regex from deleting the filename
+        # itself, assuming it was a partial filename.
+        if len(files) == 1:
+            return files[0], []
+
+        # Start with the prefix found by commonprefix,
+        # then actually make it end with a directory rather than
+        # possibly ending with part of a filename.
+        prefix = re.sub("[^/]*$", "", posixpath.commonprefix(files))
+
+        endings = []
+        for file in files:
+            ending = file[len(prefix):].strip()
+            if ending == '':
+                    ending = '.'
+            endings.append(ending)
+        return prefix, endings
+
+    """Extracts a title from commit messages"""
+    medium = 'title'
+
+    def format(self, args):
+        log = XML.dig(args.message.xml, "message", "body", "commit", "log")
+        if log:
+            return Util.extractSummary(log)
+
+
+class CommitToXHTML(CommitFormatter):
+    """Converts commit messages to XHTML, represented as a Nouvelle tag tree."""
+    medium = 'xhtml'
+    defaultComponentTree = """
+    <format xmlns:n='http://www.w3.org/1999/xhtml'>
+        <n:div style='border: 1px solid #888; background-color: #DDD; padding: 0.25em 0.5em; margin: 0em;'>
+            <autoHide> Commit by <n:strong><author/></n:strong></autoHide>
+            <autoHide> on <branch/></autoHide>
+            <n:span style='color: #888;'> :: </n:span>
+            <autoHide><n:b><version/></n:b></autoHide>
+            <autoHide>r<n:b><revision/></n:b></autoHide>
+            <n:b><module/></n:b>/<files/>:
+            <autoHide>(<url/>)</autoHide>
+        </n:div>
+        <n:div style='padding: 0em; margin: 0.5em 0em;'>
+            <log/>
+        </n:div>
+    </format>
+    """
+
+    # Use a lower width limit for HTML- web browsers typically won't wrap
+    # long paths, and it's generally easier to get to the full file tree
+    # on the web.
+    filesWidthLimit = 40
+
+    def __init__(self):
+        from LibCIA.Web import RegexTransform
+        self.hyperlinker = RegexTransform.AutoHyperlink()
+
+    def joinComponents(self, results):
+        """Nouvelle is just fine dealing with lists, don't join anything"""
+        return results
+
+    def walkComponents(self, nodes, args):
+        """Instead of concatenating lists, this implementation of walkComponents
+           nests them. This is more efficient with nouvelle, and lets us detect
+           empty results for <autoHide>.
+           """
+        results = []
+        for node in nodes:
+            results.append(self.evalComponent(node, args))
+        return results
+
+    def component_autoHide(self, element, args):
+        """The standard autoHide component is rewritten to properly recurse
+           into the contents of Nouvelle tags.
+           """
+        results = self.walkComponents(element.childNodes, args)
+        if self._checkVisibility(results):
+            return results
+        else:
+            return []
+
+    def _checkVisibility(self, nodes):
+        """Recursively check visibility for autoHide. Empty lists cause
+           us to return 0, and Nouvelle tags are recursed into.
+           """
+        for node in nodes:
+            if not node:
+                return 0
+            if isinstance(node[0], Message.MarkAsHidden):
+                return 0
+            if isinstance(node[0], tag):
+                if not self._checkVisibility(node[0].content):
+                    return 0
+        return 1
+
+    def evalComponent(self, node, args):
+        """Here we convert all components starting with 'n:' into Novuelle tags.
+           FIXME: This should really be using proper DOM namespace manipulation and such
+           """
+        if node.nodeType == node.ELEMENT_NODE and node.nodeName.startswith("n:"):
+            attrs = {}
+            for attr in node.attributes.values():
+                attrs[str(attr.name)] = attr.value
+            return [tag(node.nodeName[2:], **attrs)[ self.walkComponents(node.childNodes, args) ]]
+        return CommitFormatter.evalComponent(self, node, args)
+
+    def component_url(self, element, args):
+        element = XML.dig(args.message.xml, "message", "body", "commit", "url")
+        if element:
+            return [tag('a', href=XML.shallowText(element))[ 'link' ]]
+        else:
+            return [Message.MarkAsHidden()]
+
+    def component_log(self, element, args):
+        """Convert the log message to HTML. If the message seems to be preformatted
+           (it has some lines with indentation) it is stuck into a <pre>. Otherwise
+           it is converted to HTML by replacing newlines with <br> tags and converting
+           bulletted lists.
+           """
+        log = XML.dig(args.message.xml, "message", "body", "commit", "log")
+        if not log:
+            return []
+        content = []
+        lines = Util.getNormalizedLog(log)
+        nonListItemLines = []
+        listItems = []
+
+        if lines:
+            # Scan the message, applying a few heuristics. If we see lines
+            # that are still starting with a space after getNormalizedLog
+            # has done its thing, assume the text is preformatted. Also
+            # look for lines that appear to be list items.
+            isPreFormatted = False
+            for line in lines:
+                if line and line[0] == ' ':
+                    isPreFormatted = True
+
+                if line.startswith("* ") or line.startswith("- "):
+                    # Assume this is a list item, and convert the bullets to
+                    # a proper XHTML list.
+                    listItems.append(line[2:])
+                else:
+                    if listItems:
+                        # It's a continuation of the last item
+                        listItems[-1] = listItems[-1] + " " + line.strip()
+                    else:
+                        # If we haven't seen a list yet, stick this in nonListItemLines.
+                        # If this log message isn't a list at all, everything will end
+                        # up there but it will be safely ignored
+                        nonListItemLines.append(line)
+
+            if listItems:
+                # It looks like a bulleted list. First output the nonListItemLines,
+                # then stick the items inside a list.
+                for line in nonListItemLines:
+                    if content:
+                        content.append(tag('br'))
+                    content.append(line)
+                content = [
+                    tag('p')[ content ],
+                    tag('ul')[[ tag('li')[item] for item in listItems ]],
+                    ]
+
+            elif isPreFormatted:
+                # This is probably a preformatted message, stick it in a <pre>
+                content.append(tag('pre')[ "\n".join(lines) ])
+
+            else:
+                # Plain old text, just stick <br>s between the lines
+                for line in lines:
+                    if content:
+                        content.append(tag('br'))
+                    content.append(line)
+        else:
+            content.append(tag('i')["No log message"])
+
+        return self.hyperlinker.apply(content)
+
+
+class CommitToXHTMLLong(CommitToXHTML):
+    """Builds on the xhtml formatter to generate a longer representation of the commit,
+       suitable for a full page rather than just an item in a listing.
+       """
+    medium = 'xhtml-long'
+    defaultComponentTree = """
+    <format xmlns:n='http://www.w3.org/1999/xhtml'>
+        <n:h1>Commit Message</n:h1>
+        <headers/>
+        <n:div class='messageBody'><log/></n:div>
+        <n:h1>Modified Files</n:h1><files/>
+    </format>
+    """
+
+    _actionIcon = tag('img', width=12, height=12, _class='actionIcon')
+
+    actionIcons = {
+        'add':    _actionIcon(src='img/file_added.png',
+                              title='File Added', alt='Added'),
+        'remove': _actionIcon(src='img/file_removed.png',
+                              title='File Removed', alt='Removed'),
+        'rename': _actionIcon(src='img/file_renamed.png',
+                              title='File Renamed', alt='Renamed'),
+        'modify': _actionIcon(src='img/file_modified.png',
+                              title='File Modified', alt='Modified'),
+        }
+
+    def component_headers(self, element, args):
+        """Format all relevant commit metadata in an email-style header box"""
+        from LibCIA.Web import Template
+
+        message   = args.message
+        commit    = XML.dig(message.xml, "message", "body", "commit")
+        source    = XML.dig(message.xml, "message", "source")
+        author    = XML.dig(commit, "author")
+        version   = XML.dig(commit, "version")
+        revision  = XML.dig(commit, "revision")
+        diffLines = XML.dig(commit, "diffLines")
+        url       = XML.dig(commit, "url")
+        log       = XML.dig(commit, "log")
+        project   = XML.dig(source, "project")
+        module    = XML.dig(source, "module")
+        branch    = XML.dig(source, "branch")
+        headers   = OrderedDict()
+
+        if author:
+            headers['Author'] = XML.shallowText(author)
+        if project:
+            headers['Project'] = XML.shallowText(project)
+        if module:
+            headers['Module'] = XML.shallowText(module)
+        if branch:
+            headers['Branch'] = XML.shallowText(branch)
+        if version:
+            headers['Version'] = XML.shallowText(version)
+        if revision:
+            headers['Revision'] = XML.shallowText(revision)
+        if diffLines:
+            headers['Changed Lines'] = XML.shallowText(diffLines)
+        if url:
+            headers['URL'] = tag('a', href=XML.shallowText(url))[ Util.extractSummary(url) ]
+
+        return [Template.MessageHeaders(headers)]
+
+    def component_files(self, element, args):
+        """Format the contents of our <files> tag as a tree with nested lists"""
+        from LibCIA.Web import Template
+
+        files = XML.dig(args.message.xml, "message", "body", "commit", "files")
+        if not (files and XML.hasChildElements(files)):
+            return []
+
+        # First we organize the files into a tree of nested dictionaries.
+        # The dictionary we ultimately have FileTree render maps each node
+        # (file or directory) to a dictionary of its contents. The keys
+        # in these dictionaries can be any Nouvelle-renderable object
+        # produced by format_file.
+        #
+        # As a first step, we build a dictionary mapping path segment to
+        # [fileTag, children] lists. We then create a visual representation
+        # of each fileTag and generate the final dictionary.
+        fileTree = {}
+        for fileTag in XML.getChildElements(files):
+            if fileTag.nodeName == 'file':
+                # Separate the file into path segments and walk into our tree
+                node = [None, fileTree]
+                for segment in XML.shallowText(fileTag).split('/'):
+                    if segment:
+                        node = node[1].setdefault(segment, [None, {}])
+                # The leaf node owns this fileTag
+                node[0] = fileTag
+
+        return [Template.FileTree(self.format_file_tree(fileTree))]
+
+    def format_file_tree(self, fileTree):
+        """This is the second half of format_files- it recursively
+           converts a tree of [fileTag,children] style dictionaries
+           into a tree of Template.FileTree() compatible dicts,
+           using format_file() to render each fileTag.
+           """
+        result = {}
+        for name, t in fileTree.iteritems():
+            fileTag, children = t
+            result[self.format_file(name, fileTag)] = self.format_file_tree(children)
+        return result
+
+    def format_file(self, name, fileTag=None):
+        """Given the short name of a file, and optionally its XML tag,
+           return a Nouvelle-serializable representation.
+           """
+        if fileTag:
+            # If we have a 'uri' attribute, make this file a hyperlink
+            uri = fileTag.getAttribute('uri')
+            if uri:
+                name = tag('a', href=uri)[ name ]
+
+            # If we have an 'action' attribute, represent it with an icon
+            actionIcon = self.actionIcons.get(fileTag.getAttribute('action'))
+            if actionIcon:
+                name = (name, actionIcon)
+
+        return name
+
+### The End ###

Added: trunk/community/infrastructure/scripts/HTMLTemplate.py
===================================================================
--- trunk/community/infrastructure/scripts/HTMLTemplate.py	                        (rev 0)
+++ trunk/community/infrastructure/scripts/HTMLTemplate.py	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,744 @@
+"""HTMLTemplate - A fast, powerful, easy-to-use HTML templating system.
+
+See Manual.txt for documentation.
+"""
+
+# HTMLTemplate - A fast, powerful, easy-to-use HTML templating system.
+#
+# Copyright (C) 2004 HAS <hamish.sanderson at virgin.net>
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free 
+# Software Foundation; either version 2.1 of the License, or (at your option) 
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT 
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License 
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+__all__ = ['ParseError', 'Node', 'Template']
+
+from HTMLParser import HTMLParser
+from keyword import kwlist
+import re
+
+
+#################################################
+# SUPPORT
+#################################################
+
+def renderAtts(atts):
+    # Renders an HTML tag's attributes from a list of name-value tuples.
+    result = ''
+    for name, value in atts:
+        if value is None:
+            result += ' ' + name
+        elif '"' in value:
+            result += " %s='%s'" % (name, value)
+        else:
+            result += ' %s="%s"' % (name, value)
+    return result
+
+
+def defaultEncoder(txt):
+    # Used to HTML-encode value in 'node.content = value'.
+    return txt.replace('&', '&amp;').replace(
+            '<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
+
+def defaultDecoder(txt):
+    # Used to HTML-decode content in 'value = node.content'.
+    return txt.replace('&quot;', '"').replace(
+            '&lt;', '<').replace('&gt;', '>').replace('&amp;', '&')
+
+
+#################################################
+# TEMPLATE PARSER
+#################################################
+
+class ParseError(Exception):
+    """A template parsing error."""
+    pass
+
+
+class ElementCollector:
+    # Collects a node's type, name and content as it's parsed. 
+    # When end of node is reached, the collected data is used
+    # to construct a Template/Repeater/Container instance.
+
+    def __init__(self, *args):
+        self.nodeType, self.nodeName, self.tagName, self.atts, \
+                self.isEmpty, self.omitTags, self.shouldDelete = args
+        self.content = ['']
+        self.elementNames = {}
+        self.__depth = 1
+    
+    # Methods used to track nested tags of same tag name; used to locate
+    # close tag that marks the end of this node.
+    
+    def incDepth(self):
+        self.__depth += 1
+        
+    def decDepth(self):
+        self.__depth -= 1
+        
+    def isComplete(self):
+        return self.__depth < 1
+    
+    # Methods used to collect plain HTML and any sub-nodes.
+    
+    def addText(self, txt):
+        self.content[-1] += txt
+        
+    def addElement(self, node, nodeType, nodeName):
+        self.content.extend([node, ''])
+        self.elementNames[nodeName] = nodeType
+
+
+class Parser(HTMLParser):
+
+    def unescape(self, s):
+        # kludge: avoid HTMLParser's stupid mangling of attribute values
+        return s
+        
+    # Handles parsing events sent by parseTemplate() as it processes
+    # a template string. Collects data in ElementCollector instances, 
+    # then converts these into Template/Repeater/Container objects
+    # which it assembles into a finished object model. Stack-based.
+
+    # Regular expressions used to match special tag attributes, 
+    # a.k.a. template compiler directives, that indicate an HTML
+    # element should be converted into a template node.
+    __specialAttValuePattern = re.compile('(-)?(con|rep|sep|del):(.*)')
+    __validNodeNamePattern = re.compile('[a-zA-Z][_a-zA-Z0-9]*')
+
+    # List of words already used as property and method names,
+    # so cannot be used as template node names as well:
+    __invalidNodeNames = kwlist + [
+            'content', 'raw', 'atts', 'omittags', 'omit', 'repeat', 'render']
+    
+    def __init__(self, attribute, encoder, decoder, warn):
+        HTMLParser.__init__(self)
+        self.__specialAttributeName = attribute
+        self.__encoder = encoder
+        self.__decoder = decoder
+        self.__warn = warn
+        # Each node's content is collected in an ElementCollector instance
+        # that's stored in the __outputStack stack while it's being parsed. 
+        # Once the end of the node is found, the ElementCollector's content
+        # is used to create an instance of the appropriate node class, which
+        # is then added to previous ElementCollector in the stack (i.e. the
+        # collector for its parent node).
+        self.__outputStack = [
+                ElementCollector('tem', '', None, None, False, False, False)]
+    
+    def __isSpecialTag(self, atts, specialAttName):
+        # Determines if any of an HTML tag's attributes are
+        # compiler directives.
+        for name, value in atts:
+            if name == specialAttName:
+                matchedValue = self.__specialAttValuePattern.match(value)
+                if matchedValue:
+                    atts = dict(atts)
+                    del atts[specialAttName]
+                    omitTags, nodeType, nodeName = matchedValue.groups()
+                    return True, nodeType, nodeName, omitTags, atts
+                elif self.__warn:
+                   from warnings import warn
+                   warn("Non-directive tag attribute found: %s=%r" 
+                           % (name, value))
+        return False, '', '', False, renderAtts(atts)
+    
+    def __startTag(self, tagName, atts, isEmpty):
+        # Process an HTML tag that marks the start of an HTML element, 
+        # i.e. <foo> or <foo/>. If the tag contains a compiler directive,
+        # start collecting the element's content to be turned into a 
+        # template node, otherwise reassemble it as regular HTML markup.
+        node = self.__outputStack[-1]
+        if node.shouldDelete:
+            isSpecial = 0
+        else:
+            isSpecial, nodeType, nodeName, omitTags, atts = \
+                    self.__isSpecialTag(atts, self. __specialAttributeName)
+        if isSpecial:
+            # Verify node name is legal, then process according to 
+            # directive's type (con, rep, sep, del).
+            if nodeType != 'del' and (
+                    not self.__validNodeNamePattern.match(nodeName) 
+                    or nodeName in self.__invalidNodeNames):
+                raise ParseError, "Invalid node name: %r" % nodeName
+            shouldDelete = nodeType == 'del'
+            if node.elementNames.has_key(nodeName):
+                if node.elementNames[nodeName] == nodeType:
+                    shouldDelete = True
+                elif nodeType != 'sep':
+                    raise ParseError, ("Invalid node name: %s:%s " 
+                            "(node %s:%s already found).") % (nodeType,  
+                            nodeName, node.elementNames[nodeName], nodeName)
+            self.__outputStack.append(ElementCollector(nodeType, nodeName, 
+                    tagName, atts, isEmpty, omitTags, shouldDelete))
+        else:
+            if node.tagName == tagName:
+                # Keep track of nested open tags of same name.
+                node.incDepth()
+            if not node.shouldDelete:
+                if isEmpty:
+                    endOfOpenTag = ' />'
+                else:
+                    endOfOpenTag = '>'
+                node.addText('<' + tagName + atts + endOfOpenTag)
+    
+    def __hasCompletedElement(self, element, parent):
+        # Called by __endTag when it finds the close tag that ends an HTML
+        # element denoting a template node.
+        if element.isEmpty:
+            content = []
+        else:
+            content = element.content
+        if element.nodeType in ['con', 'rep']:
+            node = makeNode(
+                    element.nodeType, element.nodeName, element.tagName, 
+                    element.atts, content, self.__encoder, self.__decoder)
+            if element.omitTags:
+                node.omittags()
+            parent.addElement(node, element.nodeType, element.nodeName)
+        else: # element.nodeType == 'sep'
+            # Add this separator to its repeater
+            for node in parent.content[1::2]:
+                if node._nodeName == element.nodeName:
+                    if node._nodeType != 'rep':
+                        raise ParseError, ("Can't process separator node "
+                                "'sep:%s': repeater node 'rep:%s' wasn't "
+                                "found. Found node '%s:%s' instead.") % (
+                                element.nodeName, element.nodeName, 
+                                element.nodeType, element.nodeName)
+                    if element.omitTags:
+                        if content:
+                            node._sep = content[0]
+                        else:
+                            node._sep = ''
+                    else:
+                        if content:
+                            node._sep = '<%s%s>%s</%s>' % (element.tagName, 
+                                    renderAtts(element.atts.items()), # FIXED
+                                    content[0], element.tagName)
+                        else:
+                            node._sep = '<%s%s />' % (element.tagName, 
+                                    renderAtts(element.atts.items())) # FIXED
+                    return
+            raise ParseError, ("Can't process separator node 'sep:%s' in node "
+                    "'%s:%s': repeater node 'rep:%s' wasn't found.") % (
+                    element.nodeName, parent.nodeType, parent.nodeName, 
+                    element.nodeName)
+    
+    def __endTag(self, tagName, isEmpty):
+        # Process an end tag that closes an HTML element, i.e. </foo> or 
+        # <foo/>. If the tag closes an HTML element representing a template
+        # node, call __hasCompletedElement() to finish that node's creation.
+        node = self.__outputStack[-1]
+        if node.tagName == tagName:
+            # Keep track of nested close tags of same name.
+            node.decDepth()
+        if node.isComplete():
+            self.__outputStack.pop()
+            if not node.shouldDelete:
+                parent = self.__outputStack[-1]
+                self.__hasCompletedElement(node, parent)
+        elif not isEmpty:
+            node.addText('</%s>' % tagName)
+
+    def __addText(self, txt):
+        self.__outputStack[-1].addText(txt)
+    
+    # event handlers; called by HTMLParser base class.
+
+    def handle_startendtag(self, tagName, atts):
+        self.__startTag(tagName, atts, True)
+        self.__endTag(tagName, True)
+
+    def handle_starttag(self, tagName, atts):
+        self.__startTag(tagName, atts, False)
+
+    def handle_endtag(self, tagName):
+        self.__endTag(tagName, False)
+
+    def handle_charref(self, txt):
+        self.__addText('&#%s;' % txt)
+
+    def handle_entityref(self, txt):
+        self.__addText('&%s;' % txt)
+
+    def handle_data(self, txt):
+        self.__addText(txt)
+
+    def handle_comment(self, txt):
+        self.__addText('<!--%s-->' % txt)
+
+    def handle_decl(self, txt):
+        self.__addText('<!%s>' % txt)
+
+    def handle_pi(self, txt):
+        self.__addText('<?%s>' % txt)
+    
+    def result(self):
+        # Get content of template's ElementCollector once parsing is done.
+        element = self.__outputStack.pop()
+        if element.nodeType != 'tem':
+            raise ParseError, ("Can't complete template: node '%s:%s' wasn't "
+                    "correctly closed.") % (element.nodeType, element.nodeName)
+        #if len(element.content) == 1:
+        #    raise ParseError, "No special %r attributes were found." % (
+        #            self. __specialAttributeName)
+        return element.content
+
+
+#################################################
+# OBJECT MODEL CLASSES
+#################################################
+
+# Note: HTMLTemplate implements its own performance-optimised object copying 
+# system which is much faster than standard lib's general-purpose deepcopy().
+# All cloning and rendering code is optimised for speed over grokability.
+
+# To summarise, cloning and rendering involves bouncing between a template
+# node's various base classes to perform the various steps of each operation
+# with child nodes being processed recursively. When cloning, all child nodes
+# are cloned as a single operation. When rendering, rather than wait until all
+# processing is complete, Repeaters perform a mini-render of their content
+# as soon as they finish rendering each clone of themselves. This reduces
+# the number of template object instances that build up during rendering,
+# reducing memory overheads, and is a bit faster than performing two separate
+# traversals, one to call the template's Controller functions to insert 
+# content, and another to traverse the (by now very large) template object
+# model to extract the finished HTML. 
+
+# Note that the one disadvantage of using incremental rendering is that it 
+# makes it awkward for an asynchronous system to acquire new template objects 
+# in advance and put them into storage for later processing. By the time the 
+# system digs these nodes back out of storage and fills them in, their content
+# has long since been rendered by some Repeater node, so any further changes
+# will not appear in the final page. Should anyone ever need to use 
+# HTMLTemplate in this fashion they can, of course, redesign the rendering
+# system so that all nodes created remain viable until a final, separate 
+# 'render everything' message is sent through the entire object model, but
+# it'll obviously cost extra in both performance and memory overheads - 
+# which is why it's not used as the standard operating model.
+
+class CloneNode(object):
+    """Makes cloned nodes."""
+    def __init__(self, node):
+        self.__dict__ = node.__dict__.copy()
+        self.__class__ = node.__class__
+
+# Node classes provide the basic functionality for a template node, with
+# additional functionality being added by the Content classes. The Python
+# implementation uses multiple inheritance to compose these behaviours;
+# languages with native support for mixins or delegates may find those
+# more appropriate (and easier for developers to follow).
+
+class Node:
+    """Abstract base class for template nodes; used for type checking when 
+       user replaces an existing template node with a new one.
+    """
+    pass
+
+class Container(Node):
+    """A Container node has a one-to-one relationship with the node 
+       that contains it.
+    """
+    
+    _nodeType = 'con'
+    
+    def __init__(self, nodeName, tagName, atts):
+        self._nodeName = nodeName
+        self._atts = dict(atts) # On cloning, shallow copy this dict.
+        if isinstance(self, NullContent):
+            self.__startTag = '<%s%%s />' % tagName
+            self.__endTag = ''
+        else:
+            self.__startTag = '<%s%%s>' % tagName
+            self.__endTag = '</%s>' % tagName
+        self.__omitTags = False
+        self._omit = False
+    
+    def _clone(self):
+        clone = CloneNode(self)
+        clone._atts = self._atts.copy()
+        return clone
+    
+    def _renderNode(self, collector):
+        # Adds node's content to supplied collector list. Tags are added
+        # here; PlainContent/RichContent's _renderContent() adds content.
+        if self.__omitTags:
+            self._renderContent(collector)
+        else:
+            collector.append(self.__startTag % renderAtts(self._atts.items()))
+            self._renderContent(collector)
+            collector.append(self.__endTag)
+
+    def _render(self, collector):
+        # Called by parent node to render this node and all its contents.
+        if not self._omit:
+            self._renderNode(collector)
+    
+    def __attsGet(self):
+        return Attributes(self._atts, self._encode, self._decode)
+    
+    def __attsSet(self, val):
+        self._atts = {}
+        atts = Attributes(self._atts, self._encode, self._decode)
+        for name, value in val.items():
+            atts[name] = value
+    
+    atts = property(__attsGet, __attsSet, 
+            doc="Get this element's tag attributes.")
+    
+    def omittags(self):
+        """Don't render this element's tag(s)."""
+        self.__omitTags = True
+    
+    def omit(self):
+        """Don't render this element."""
+        self._omit = True
+
+
+class Repeater(Container):
+    """A Repeater node has a one-to-many relationship with the node
+       that contains it.
+    """
+    
+    _nodeType = 'rep'
+    
+    def __init__(self, nodeName, tagName, atts):
+        self._sep = '\n'
+        self.__renderedContent = [] # On cloning, shallow-copy this list.
+        Container.__init__(self, nodeName, tagName, atts)
+        
+    _fastClone = Container._clone
+    
+    def _clone(self):
+        clone = Container._clone(self)
+        clone.__renderedContent = self.__renderedContent[:]
+        return clone
+    
+    def _render(self, collector):
+        # Called by parent node to render this node and all its contents.
+        if not self._omit:
+            collector.extend(self.__renderedContent[1:])
+    
+    def repeat(self, fn, list, *args):
+        """Render an instance of this node for each item in list."""
+        self.__renderedContent = collector = [] # replaces any previous content
+        # For each item in list, copy this node and pass it and the list item 
+        # to the supplied callback function. Once the function has finished
+        # inserting data into the cloned node, perform a mini-render of its
+        # content and add the result to self.__renderedContent - where it will
+        # remain until the original node is finally rendered by its parent.
+        for item in list:
+            clone = self._fastClone()
+            fn(clone, item, *args)
+            if not clone._omit:
+                collector.append(clone._sep)
+                clone._renderNode(collector)
+
+##
+
+class Attributes:
+    """Public facade for modifying a node's tag attributes. Behaves like
+       a much simplified dict object. Vended by Node's atts property.
+    """
+    
+    __attNamePattern = re.compile('^[a-zA-Z_][-.:a-zA-Z_0-9]*$')
+    
+    def __init__(self, atts, encoder, decoder):
+        self.__atts = atts # The Node's tag attributes dict.
+        self.__encode = encoder
+        self.__decode = decoder
+    
+    def __getitem__(self, name):
+        return self.__decode(self.__atts[name])
+        
+    def __setitem__(self, name, val):
+        try:
+            if not self.__attNamePattern.match(name): # Note: this 
+            # will throw a TypeError if 'name' is not string/unicode.
+                raise KeyError, "bad name."
+            if val != None:
+                if not isinstance(val, basestring):
+                    raise TypeError, "bad value: %r" % val
+                val = self.__encode(val)
+                if '"' in val and "'" in val:
+                    raise ValueError, "value %r contains " \
+                            "both single and double quotes." % val
+            self.__atts[name] = val
+        except Exception, e:
+            msg = str(e)
+            if not isinstance(name, basestring):
+                msg = "bad name."
+            raise e.__class__, "Can't set tag attribute %r: %s" % (name, msg)
+        
+    def __delitem__(self, name):
+        del self.__atts[name]
+    
+    def __repr__(self):
+        return '<Attributes [%s]>' % renderAtts(self.__atts.items())[1:]
+
+
+#######
+
+# Content classes provide nodes representing non-empty HTML elements with
+# support for containing plain HTML content/sub-nodes.
+
+class Content(object):
+    def __init__(self, encoder, decoder):
+        self._encode = encoder
+        self._decode = decoder
+    
+    def _printStructure(self, indent):
+        print indent + self._nodeType + ':' + self._nodeName
+
+##
+
+class NullContent(Content):
+    """Represents an empty HTML element's non-existent content."""
+    
+    def _renderContent(self, collector):
+        pass
+
+
+class PlainContent(Content):
+    """Represents a non-empty HTML element's content where it contains plain 
+       text/markup only.
+    """
+    
+    def __init__(self, content, encoder, decoder):
+        Content.__init__(self, encoder, decoder)
+        self.raw = content # Get/Set this element's content as raw markup;
+        # use with care.
+        
+    def _renderContent(self, collector):
+        # Called by Node classes to add HTML element's content.
+        collector.append(self.raw)
+    
+    def __contentGet(self):
+        return self._decode(self.raw)
+    
+    def __contentSet(self, txt):
+        self.raw = self._encode(txt)
+    
+    content = property(__contentGet, __contentSet, 
+            doc="Get/Set this element's content as escaped text.")
+
+
+class RichContent(Content):
+    """Represents a non-empty HTML element's content where it contains other 
+       Container/Repeater nodes.
+    """
+    
+    __validIdentifierPattern = re.compile('^[a-zA-Z_][a-zA-Z_0-9]*$')
+    
+    # KLUDGE: The following line keeps Python 2.3 sweet while it instantiates 
+    # instances of this class; without it, the process crashes hard as 
+    # __init__ conflicts with __setattr__.
+    __nodesDict = {}
+    
+    def __init__(self, content, encoder, decoder):
+        Content.__init__(self, encoder, decoder)
+        self.__nodesList = content # On cloning, deep copy this list.
+        self.__nodesDict = dict(
+                [(node._nodeName, node) for node in content[1::2]]) # (On clon-
+        # ing: replace with a new dict built from cloned self.__nodesList.)
+    
+    def __rawGet(self):
+        if self.__nodesDict:
+            raise RuntimeError, ("Can't get raw/content of a node that "
+                    "contains other nodes.")
+        else:
+            return self.__nodesList[0]
+    
+    # Note: property setting is done by __setattr__(), which takes precedence
+    # over property-based setters for some reason.
+    
+    raw = property(__rawGet, 
+            doc="Get/Set this element's raw content.")
+    
+    content = property(lambda self:self._decode(self.__rawGet()),
+            doc="Get/Set this element's content as escaped text.")
+    
+    def _initRichClone(self, clone):
+        # Once node is cloned, this is called to clone its sub-nodes.
+        clone.__nodesDict = {}
+        L = clone.__nodesList = self.__nodesList[:]
+        for i in range(1, len(L), 2):
+            clone.__nodesDict[L[i]._nodeName] = L[i] = L[i]._clone()
+        return clone
+    
+    def _renderContent(self, collector):
+        # Called by Node classes to add HTML element's content.
+        L = self.__nodesList
+        collector.append(L[0])
+        for i in range(1, len(L), 2):
+            L[i]._render(collector)
+            collector.append(L[i + 1])
+    
+    def _printStructure(self, indent):
+        Content._printStructure(self, indent)
+        for node in self.__nodesList[1::2]:
+            node._printStructure(indent + '\t')
+    
+    def __getattr__(self, name):
+        # Get a sub-node.
+        if self.__nodesDict.has_key(name):
+            return self.__nodesDict[name]
+        else:
+            raise AttributeError , "%s instance has no attribute %r." % (
+                    self.__class__.__name__, name)
+    
+    def __setattr__(self, name, value):
+        # Replace a sub-node, or replace node's content.
+        if self.__nodesDict.has_key(name):
+            if not isinstance(value, Node):
+                # Note: This type check is to catch careless user mistakes like
+                # 'node.foo = "text"' instead of  'node.foo.content = "text"'
+                raise TypeError, ("Can't replace node '%s:%s': value isn't a "
+                        "Node object.") % (self.__nodesDict[name]._nodeType,
+                         self.__nodesDict[name]._nodeName)
+            value = value._clone() 
+            value._nodeName = name
+            idx = self.__nodesList.index(self.__nodesDict[name])
+            self.__nodesDict[name] = self.__nodesList[idx] = value
+        elif name == 'content':
+            self.__nodesList = [self._encode(value)]
+            self.__nodesDict = {}
+        elif name == 'raw':
+            self.__nodesList = [value]
+            self.__nodesDict = {}
+        else:
+            self.__dict__[name] = value
+
+
+#######
+# Note: Container and Repeater objects are instantiated via the makeNode()
+# constructor function. This returns the appropriate class for the content 
+# supplied ('abstract factory'). Container and Repeater nodes are actually
+# represented by three different classes apiece, depending on whether they
+# represent empty or non-empty HTML elements and, in the case of the latter,
+# whether or not they contain any sub-nodes. The documentation glosses over
+# these details for simplicity, since the user doesn't need to know the
+# exact class of a node in order to use it.
+
+class EmptyContainer(NullContent, Container):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        NullContent.__init__(self, encoder, decoder)
+        Container.__init__(self, nodeName, tagName, atts)
+
+
+class PlainContainer(PlainContent, Container):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        PlainContent.__init__(self, content[0], encoder, decoder)
+        Container.__init__(self, nodeName, tagName, atts)
+
+
+class RichContainer(RichContent, Container):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        RichContent.__init__(self, content, encoder, decoder)
+        Container.__init__(self, nodeName, tagName, atts)
+        
+    def _clone(self):
+        return self._initRichClone(Container._clone(self))
+
+##
+
+class EmptyRepeater(NullContent, Repeater):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        NullContent.__init__(self, encoder, decoder)
+        Repeater.__init__(self, nodeName, tagName, atts)
+
+
+class PlainRepeater(PlainContent, Repeater):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        PlainContent.__init__(self, content[0], encoder, decoder)
+        Repeater.__init__(self, nodeName, tagName, atts)
+
+
+class RichRepeater(RichContent, Repeater):
+    def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
+        RichContent.__init__(self, content, encoder, decoder)
+        Repeater.__init__(self, nodeName, tagName, atts)
+        
+    def _clone(self):
+        return self._initRichClone(Repeater._clone(self))
+        
+    def _fastClone(self):
+        return self._initRichClone(Repeater._fastClone(self))
+
+##
+
+__nodeClasses = {
+        'con': {'empty': EmptyContainer, 
+                'plain': PlainContainer, 
+                'rich': RichContainer},
+        'rep': {'empty': EmptyRepeater, 
+                'plain': PlainRepeater, 
+                'rich': RichRepeater}}
+
+def makeNode(nodeType, nodeName, tagName, atts, content, encoder, decoder):
+    # Called by template parser.
+    return __nodeClasses[nodeType][{0: 'empty', 1: 'plain'}.get(len(content), 
+            'rich')](nodeName, tagName, atts, content, encoder, decoder)
+
+
+#################################################
+# MAIN
+#################################################
+
+class Template(RichContent):
+    """The top-level (i.e. root) node of the template object model."""
+    
+    _nodeType = 'tem'
+    _nodeName = ''
+    
+    def __init__(self, callback, html, attribute='node', 
+            codecs=(defaultEncoder, defaultDecoder), warnings=False):
+        """
+            callback : function -- the function that controls how this
+                    template is rendered
+            html : string or unicode -- the template HTML
+            [attribute : string or unicode] -- name of the tag attribute used
+                    to hold compiler directives
+            [codecs : tuple] -- a tuple containing two functions used by the 
+                    content property to encode/decode HTML entities
+            [warnings : boolean] -- warn when non-directive attribute
+                    is encountered
+        """
+        self.__callback = callback
+        parser = Parser(attribute, codecs[0], codecs[1], warnings)
+        parser.feed(html)
+        parser.close()
+        RichContent.__init__(self, parser.result(), *codecs)
+    
+    def render(self, *args):
+        """Render this template; *args will be passed directly to the template.
+        """
+        clone = self._initRichClone(CloneNode(self))
+        self.__callback(clone, *args)
+        collector = []
+        clone._renderContent(collector)
+        try: # quick-n-dirty error reporting; not a real substitute for type-
+            # checking for bad value assignments at point of origin, but cheap
+            return ''.join(collector)
+        except TypeError:
+            raise TypeError, ("Can't render template: some node's content was "
+                    "set to a non-text value.")
+
+    def structure(self):
+        """Print the object model's structure for diagnostic use."""
+        print '-' * 80
+        self._printStructure('')
+        print '-' * 80
+

Added: trunk/community/infrastructure/scripts/NEEDED
===================================================================
--- trunk/community/infrastructure/scripts/NEEDED	                        (rev 0)
+++ trunk/community/infrastructure/scripts/NEEDED	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,5 @@
+python-twisted-core
+python-twisted-web
+python-mysqldb
+python-zopeinterface
+python-nouvelle

Added: trunk/community/infrastructure/scripts/Tools.py
===================================================================
--- trunk/community/infrastructure/scripts/Tools.py	                        (rev 0)
+++ trunk/community/infrastructure/scripts/Tools.py	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,302 @@
+#!/usr/bin/python -Ot
+
+#
+# This Python script is:
+#  (C) 2007, David Paleino <d.paleino at gmail.com>
+#
+# It is licensed under the terms of GNU General Public License (GPL) v2 or later.
+#
+
+import os
+import re
+
+base = "/var/lib/gforge/chroot/home/groups/debian-med"
+tasks_repos = "svn://svn.debian.org/svn/cdd/projects/med/trunk/debian-med/tasks/"
+
+tasks = "%s/data/tasks" % base
+
+#
+# This is used to parse Task files.
+#
+
+packages = []
+
+def inner_parseTask(file, doSeparate):
+	global packages
+
+	if doSeparate:
+		name = file.name.split("/")[-1]
+		if not type(packages) == dict:
+			packages = {}
+		if name not in packages:
+			packages[name] = []
+		stepone = {}
+		if name not in stepone:
+			stepone[name] = []
+		steptwo = {}
+		if name not in steptwo:
+			steptwo[name] = []
+	else:
+		stepone = []
+		steptwo = []
+
+	pattern = re.compile(r"Depends:")
+	continued = False
+	for line in file.readlines():
+		match = pattern.match(line)
+		if match and not continued:
+			if doSeparate:
+				stepone[name].append(line.replace("Depends:", "").strip().split(","))
+			else:
+				stepone.append(line.replace("Depends:", "").strip().split(","))
+			if line.strip().endswith("\\") == True:
+				continued = True
+				continue
+		elif continued == True:
+			if doSeparate:
+				stepone[name].append(line.strip().split(","))
+			else:
+				stepone.append(line.strip().split(","))
+			if line.strip().endswith("\\") == True:
+				continued = True
+			else:
+				continued = False
+
+			continue
+
+	if doSeparate:
+		for item in stepone[name]:
+			if type(item) == str:
+				steptwo[name].append(item.split("|"))
+			elif type(item) == list:
+				for inner in item:
+					steptwo[name].append(inner.split("|"))
+
+		for item in steptwo[name]:
+			if type(item) == str and item != "":
+				packages[name].append(item.strip())
+			elif type(item) == list:
+				for inner in item:
+					if inner != "":
+						packages[name].append(inner.strip())
+		steptwo[name] = packages[name]
+		try:
+			packages[name] = list(set(steptwo[name].remove('\\')))
+		except:
+			packages[name] = list(set(steptwo[name]))
+
+		packages[name].sort()
+	else:
+		for item in stepone:
+			if type(item) == str:
+				steptwo.append(item.split("|"))
+			elif type(item) == list:
+				for inner in item:
+					steptwo.append(inner.split("|"))
+
+		for item in steptwo:
+			if type(item) == str and item != "":
+				packages.append(item.strip())
+			elif type(item) == list:
+				for inner in item:
+					if inner != "":
+						packages.append(inner.strip())
+
+		steptwo = packages
+		try:
+			packages = list(set(steptwo.remove('\\')))
+		except:
+			packages = list(set(steptwo))
+
+		packages.sort()
+
+def parseTasks(name = None, doSeparate = False):
+	#
+	# Let's get our task files
+	#
+
+	os.system("svn co %s %s >> /dev/null" % (tasks_repos, tasks))
+
+	if not name:
+		for file in os.listdir(tasks):
+			if os.path.isfile("%s/%s" % (tasks, file)):
+				f = open("%s/%s" % (tasks, file))
+				inner_parseTask(f, doSeparate)
+				f.close()
+	else:
+		if os.path.isfile("%s/%s" % (tasks, name)):
+			f = open("%s/%s" % (tasks, name))
+			inner_parseTask(f, doSeparate)
+			f.close()
+
+	return packages
+
+def parseTasksNonOff(task = None):
+	pkg_desc = {}
+
+	regex = r"""Depends: (?P<package>.*)
+Homepage: (?P<homepage>.*)
+Pkg-URL: (?P<deburl>.*)
+License: (?P<license>.*)
+Pkg-Description: (?P<short>.*)
+(?P<long>(^ .*$\n)+\n?)"""
+
+	p = re.compile(regex, re.MULTILINE)
+	if task:
+		try:
+			if os.path.isfile("%s/%s" % (tasks, task)):
+				f = open("%s/%s" % (tasks, task))
+				tuples = p.findall(f.read())
+				for group in tuples:
+					(package, homepage, deburl, license, short, long, foo) = group
+					pkg_desc[package] = {}
+					pkg_desc[package]['Homepage'] = homepage
+					pkg_desc[package]['Pkg-URL'] = deburl
+					pkg_desc[package]['License'] = license
+					pkg_desc[package]['ShortDesc'] = short
+					pkg_desc[package]['LongDesc'] = long.replace("\n .\n", "<br /><br />").replace("\n", "")
+
+				f.close()
+		except:
+			# Check all task files if the specified one
+			# does not exist.
+			parseTasksNonOff(None)
+	else:
+		for file in os.listdir(tasks):
+			if os.path.isfile("%s/%s" % (tasks, file)):
+				f = open("%s/%s" % (tasks, file))
+				tuples = p.findall(f.read())
+				for group in tuples:
+					(package, homepage, deburl, license, short, long, foo) = group
+					if file not in pkg_desc:
+						pkg_desc[file] = []
+					tmp = {}
+					tmp['Package'] = package
+					tmp['Homepage'] = homepage
+					tmp['Pkg-URL'] = deburl
+					tmp['License'] = license
+					tmp['ShortDesc'] = short
+					tmp['LongDesc'] = long.replace("\n .\n", "<br /><br />").replace("\n", "")
+
+					pkg_desc[file].append(tmp)
+
+				f.close()
+
+	return pkg_desc
+
+def parseTasksUnavail(task = None):
+	pkg_desc = {}
+
+	regex = r"""Depends:(?P<package>.*)
+Homepage:(?P<homepage>.*)
+Responsible:(?P<responsible>.*)
+License:(?P<license>.*)
+WNPP:(?P<wnpp>.*)
+Pkg-Description:(?P<short>.*)
+(?P<long>(^ .*$\n)+\n?)"""
+
+	p = re.compile(regex, re.MULTILINE)
+	if task:
+		try:
+			if os.path.isfile("%s/%s" % (tasks, task)):
+				f = open("%s/%s" % (tasks, task))
+				tuples = p.findall(f.read())
+				for group in tuples:
+					(package, homepage, responsible, license, wnpp, short, long, foo) = group
+					pkg_desc[package] = {}
+					pkg_desc[package]['Homepage'] = homepage.strip()
+					pkg_desc[package]['Responsible'] = responsible.strip()
+					pkg_desc[package]['License'] = license.strip()
+					pkg_desc[package]['WNPP'] = wnpp.strip()
+					pkg_desc[package]['ShortDesc'] = short.strip()
+					pkg_desc[package]['LongDesc'] = long.replace("\n .\n", "<br /><br />").replace("\n", "")
+
+				f.close()
+		except:
+			# Check all task files if the specified one
+			# does not exist.
+			parseTasksNonOff(None)
+	else:
+		for file in os.listdir(tasks):
+			if os.path.isfile("%s/%s" % (tasks, file)):
+				f = open("%s/%s" % (tasks, file))
+				tuples = p.findall(f.read())
+				for group in tuples:
+					(package, homepage, responsible, license, wnpp, short, long, foo) = group
+					if file not in pkg_desc:
+						pkg_desc[file] = []
+					tmp = {}
+					tmp['Package'] = package
+					tmp['Homepage'] = homepage.strip()
+					tmp['Responsible'] = responsible.strip()
+					tmp['License'] = license.strip()
+					tmp['WNPP'] = wnpp.strip()
+					tmp['ShortDesc'] = short.strip()
+					tmp['LongDesc'] = long.replace("\n .\n", "<br /><br />").replace("\n", "")
+
+					pkg_desc[file].append(tmp)
+
+				f.close()
+
+	return pkg_desc
+
+def parseTaskDetails(file = None):
+	det = {}
+
+	regex = r"""Task: (?P<task>.*)
+Description: (?P<short>.*)
+(?P<long>(^ .*$\n)+\n?)"""
+
+	p = re.compile(regex, re.MULTILINE)
+	if file:
+		try:
+			if os.path.isfile("%s/%s" % (tasks, file)):
+				f = open("%s/%s" % (tasks, file))
+				m = p.search(f.read())
+				if m:
+					det['Task'] = m.group("task")
+					det['ShortDesc'] = m.group("short")
+					det['LongDesc'] = m.group("long").replace("\n .\n", "<br /><br />").replace("\n", "")
+
+				f.close()
+		except:
+			# Check all task files if the specified one
+			# does not exist.
+			parseTasksNonOff(None)
+	else:
+		for file in os.listdir(tasks):
+			if os.path.isfile("%s/%s" % (tasks, file)):
+				f = open("%s/%s" % (tasks, file))
+				m = p.search(f.read())
+				if m:
+					if file not in det:
+						det[file] = {}
+					det[file]['Task'] = m.group("task")
+					det[file]['ShortDesc'] = m.group("short")
+					det[file]['LongDesc'] = m.group("long")
+
+				f.close()
+
+	return det
+
+def grep(search, file):
+	try:
+		f = open(file, "r")
+	except:
+		return False
+
+	try:
+		p = re.compile(r"%s" % re.escape(search))
+	except:
+		print "ERROR:"
+		print "    searching %s in %s" % (search, file)
+		print "    escaped search: %s" % re.escape(search)
+		return False
+
+	for line in f.readlines():
+		m = p.match(line)
+		if m:
+			return True
+
+	f.close()
+	return False

Added: trunk/community/infrastructure/scripts/check-static
===================================================================
--- trunk/community/infrastructure/scripts/check-static	                        (rev 0)
+++ trunk/community/infrastructure/scripts/check-static	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+BASE=/var/lib/gforge/chroot/home/groups/debian-med
+
+cp -rf $BASE/static/* $BASE/htdocs/


Property changes on: trunk/community/infrastructure/scripts/check-static
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/ciabot_svn.py
===================================================================
--- trunk/community/infrastructure/scripts/ciabot_svn.py	                        (rev 0)
+++ trunk/community/infrastructure/scripts/ciabot_svn.py	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,398 @@
+#!/usr/bin/env python
+#
+# This is a CIA client script for Subversion repositories, written in python.
+# It generates commit messages using CIA's XML format, and can deliver them
+# using either XML-RPC or email. See below for usage and cuztomization
+# information.
+#
+# --------------------------------------------------------------------------
+#
+# Copyright (c) 2004-2007, Micah Dowty
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   * Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#   * The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# --------------------------------------------------------------------------
+#
+# This script is cleaner and much more featureful than the shell
+# script version, but won't work on systems without Python.
+#
+# To use the CIA bot in your Subversion repository...
+#
+# 1. Customize the parameters below
+#
+# 2. This script should be called from your repository's post-commit
+#    hook with the repository and revision as arguments. For example,
+#    you could copy this script into your repository's "hooks" directory
+#    and add something like the following to the "post-commit" script,
+#    also in the repository's "hooks" directory:
+#
+#      REPOS="$1"
+#      REV="$2"
+#      $REPOS/hooks/ciabot_svn.py "$REPOS" "$REV" &
+#
+#    Or, if you have multiple project hosted, you can add each
+#    project's name to the commandline in that project's post-commit
+#    hook:
+#
+#      $REPOS/hooks/ciabot_svn.py "$REPOS" "$REV" "ProjectName" &
+#
+############# There are some parameters for this script that you can customize:
+
+class config:
+    # Replace this with your project's name, or always provide a project
+    # name on the commandline.
+    #
+    # NOTE: This shouldn't be a long description of your project. Ideally
+    #       it is a short identifier with no spaces, punctuation, or
+    #       unnecessary capitalization. This will be used in URLs related
+    #       to your project, as an internal identifier, and in IRC messages.
+    #       If you want a longer name shown for your project on the web
+    #       interface, please use the "title" metadata key rather than
+    #       putting that here.
+    #
+    project = "debian-med"
+
+    # Subversion's normal directory hierarchy is powerful enough that
+    # it doesn't have special methods of specifying modules, tags, or
+    # branches like CVS does.  Most projects do use a naming
+    # convention though that works similarly to CVS's modules, tags,
+    # and branches.
+    #
+    # This is a list of regular expressions that are tested against
+    # paths in the order specified. If a regex matches, the 'branch'
+    # and 'module' groups are stored and the matching section of the
+    # path is removed.
+    #
+    # Several common directory structure styles are below as defaults.
+    # Uncomment the ones you're using, or add your own regexes.
+    # Whitespace in the each regex are ignored.
+
+    pathRegexes = [
+        r"^ trunk/community|packages/(?P<module>[^/]+)/ "
+    #   r"^ (branches|tags)/ (?P<branch>[^/]+)/ ",
+    #   r"^ (branches|tags)/ (?P<module>[^/]+)/ (?P<branch>[^/]+)/ ",
+        ]
+
+    # If your repository is accessible over the web, put its base URL here
+    # and 'uri' attributes will be given to all <file> elements. This means
+    # that in CIA's online message viewer, each file in the tree will link
+    # directly to the file in your repository.
+    repositoryURI = None
+
+    # If your repository is accessible over the web via a tool like ViewVC 
+    # that allows viewing information about a full revision, put a format string
+    # for its URL here. You can specify various substitution keys in the Python
+    # syntax: "%(project)s" is replaced by the project name, and likewise
+    # "%(revision)s" and "%(author)s" are replaced by the revision / author.
+    # The resulting URI is added to the data sent to CIA. After this, in CIA's
+    # online message viewer, the commit will link directly to the corresponding
+    # revision page.
+    revisionURI = None
+    # Example (works for ViewVC as used by SourceForge.net):
+    #revisionURI = "https://svn.sourceforge.net/viewcvs.cgi/%(project)s?view=rev&rev=%(revision)s"
+
+    # This can be the http:// URI of the CIA server to deliver commits over
+    # XML-RPC, or it can be an email address to deliver using SMTP. The
+    # default here should work for most people. If you need to use e-mail
+    # instead, you can replace this with "cia at cia.navi.cx"
+    server = "http://cia.navi.cx"
+
+    # The SMTP server to use, only used if the CIA server above is an
+    # email address.
+    smtpServer = "localhost"
+
+    # The 'from' address to use. If you're delivering commits via email, set
+    # this to the address you would normally send email from on this host.
+    fromAddress = "debian-med-commit at lists.alioth.debian.org"
+
+    # When nonzero, print the message to stdout instead of delivering it to CIA.
+    debug = 0
+
+
+############# Normally the rest of this won't need modification
+
+import sys, os, re, urllib, getopt
+
+class File:
+    """A file in a Subversion repository. According to our current
+    configuration, this may have a module, branch, and URI in addition
+    to a path."""
+
+    # Map svn's status letters to our action names
+    actionMap = {
+        'U': 'modify',
+        'A': 'add',
+        'D': 'remove',
+        }
+
+    def __init__(self, fullPath, status=None):
+        self.fullPath = fullPath
+        self.path = fullPath
+        self.action = self.actionMap.get(status)
+
+    def getURI(self, repo):
+        """Get the URI of this file, given the repository's URI. This
+        encodes the full path and joins it to the given URI."""
+        quotedPath = urllib.quote(self.fullPath)
+        if quotedPath[0] == '/':
+            quotedPath = quotedPath[1:]
+        if repo[-1] != '/':
+            repo = repo + '/'
+        return repo + quotedPath
+
+    def makeTag(self, config):
+        """Return an XML tag for this file, using the given config"""
+        attrs = {}
+
+        if config.repositoryURI is not None:
+            attrs['uri'] = self.getURI(config.repositoryURI)
+
+        if self.action:
+            attrs['action'] = self.action
+
+        attrString = ''.join([' %s="%s"' % (key, escapeToXml(value,1))
+                              for key, value in attrs.items()])
+        return "<file%s>%s</file>" % (attrString, escapeToXml(self.path))
+
+
+class SvnClient:
+    """A CIA client for Subversion repositories. Uses svnlook to
+    gather information"""
+    name = 'Python Subversion client for CIA'
+    version = '1.20'
+
+    def __init__(self, repository, revision, config):
+        self.repository = repository
+        self.revision = revision
+        self.config = config
+
+    def deliver(self, message):
+        if config.debug:
+            print message
+        else:
+            server = self.config.server
+            if server.startswith('http:') or server.startswith('https:'):
+                # Deliver over XML-RPC
+                import xmlrpclib
+                xmlrpclib.ServerProxy(server).hub.deliver(message)
+            else:
+                # Deliver over email
+                import smtplib
+                smtp = smtplib.SMTP(self.config.smtpServer)
+                smtp.sendmail(self.config.fromAddress, server,
+                              "From: %s\r\nTo: %s\r\n"
+                              "Subject: DeliverXML\r\n\r\n%s" %
+                              (self.config.fromAddress, server, message))
+
+    def main(self):
+        self.collectData()
+        self.deliver("<message>" +
+                     self.makeGeneratorTag() +
+                     self.makeSourceTag() +
+                     self.makeBodyTag() +
+                     "</message>")
+
+    def makeAttrTags(self, *names):
+        """Given zero or more attribute names, generate XML elements for
+           those attributes only if they exist and are non-None.
+           """
+        s = ''
+        for name in names:
+            if hasattr(self, name):
+                v = getattr(self, name)
+                if v is not None:
+                    # Recent Pythons don't need this, but Python 2.1
+                    # at least can't convert other types directly
+                    # to Unicode. We have to take an intermediate step.
+                    if type(v) not in (type(''), type(u'')):
+                        v = str(v)
+                    
+                    s += "<%s>%s</%s>" % (name, escapeToXml(v), name)
+        return s
+
+    def makeGeneratorTag(self):
+        return "<generator>%s</generator>" % self.makeAttrTags(
+            'name',
+            'version',
+            )
+
+    def makeSourceTag(self):
+        return "<source>%s</source>" % self.makeAttrTags(
+            'project',
+            'module',
+            'branch',
+            )
+
+    def makeBodyTag(self):
+        return "<body><commit>%s%s</commit></body>" % (
+            self.makeAttrTags(
+            'revision',
+            'author',
+            'log',
+            'diffLines',
+            'url',
+            ),
+            self.makeFileTags(),
+            )
+
+    def makeFileTags(self):
+        """Return XML tags for our file list"""
+        return "<files>%s</files>" % ''.join([file.makeTag(self.config)
+                                              for file in self.files])
+
+    def svnlook(self, command):
+        """Run the given svnlook command on our current repository and
+        revision, returning all output"""
+        # We have to set LC_ALL to force svnlook to give us UTF-8 output,
+        # then we explicitly slurp that into a unicode object.
+        return unicode(os.popen(
+            'LC_ALL="en_US.UTF-8" svnlook %s -r "%s" "%s"' %
+            (command, self.revision, self.repository)).read(),
+            'utf-8', 'replace')
+
+    def collectData(self):
+        self.author = self.svnlook('author').strip()
+        self.project = self.config.project
+        self.log = self.svnlook('log')
+        self.diffLines = len(self.svnlook('diff').split('\n'))
+        self.files = self.collectFiles()
+        if self.config.revisionURI is not None:
+            self.url = self.config.revisionURI % self.__dict__
+        else:
+            self.url = None
+
+    def collectFiles(self):
+        # Extract all the files from the output of 'svnlook changed'
+        files = []
+        for line in self.svnlook('changed').split('\n'):
+            path = line[2:].strip()
+            if path:
+                status = line[0]
+                files.append(File(path, status))
+
+        # Try each of our several regexes. To be applied, the same
+        # regex must mach every file under consideration and they must
+        # all return the same results. If we find one matching regex,
+        # or we try all regexes without a match, we're done.
+        matchDict = None
+        for regex in self.config.pathRegexes:
+            matchDict = matchAgainstFiles(regex, files)
+            if matchDict is not None:
+                self.__dict__.update(matchDict)
+                break
+
+        return files
+
+
+def matchAgainstFiles(regex, files):
+    """Try matching a regex against all File objects in the provided list.
+       If the regex returns the same matches for every file, the matches
+       are returned in a dict and the matched portions are filtered out.
+       If not, returns None.
+       """
+    prevMatchDict = None
+    compiled = re.compile(regex, re.VERBOSE)
+    for f in files:
+
+        match = compiled.match(f.fullPath)
+        if not match:
+            # Give up, it must match every file
+            return None
+
+        matchDict = match.groupdict()
+        if prevMatchDict is not None and prevMatchDict != matchDict:
+            # Give up, we got conflicting matches
+            return None
+
+        prevMatchDict = matchDict
+
+    # If we got this far, the regex matched every file with
+    # the same results.  Now filter the matched portion out of
+    # each file and store the matches we found.
+    for f in files:
+        f.path = compiled.sub('', f.fullPath)
+    return prevMatchDict
+
+
+def escapeToXml(text, isAttrib=0):
+    text = unicode(text)
+    text = text.replace("&", "&amp;")
+    text = text.replace("<", "&lt;")
+    text = text.replace(">", "&gt;")
+    if isAttrib == 1:
+        text = text.replace("'", "&apos;")
+        text = text.replace("\"", "&quot;")
+    return text
+
+
+def usage():
+    """Print a short usage description of this script and exit"""
+    sys.stderr.write("Usage: %s [OPTIONS] REPOS-PATH REVISION [PROJECTNAME]\n" %
+                      sys.argv[0])
+
+
+def version():
+    """Print out the version of this script"""
+    sys.stderr.write("%s %s\n" % (sys.argv[0], SvnClient.version))
+
+
+def main():
+    try:
+        options = [ "version" ]
+        for key in config.__dict__:
+            if not key.startswith("_"):
+                options.append(key + "=");
+        opts, args = getopt.getopt(sys.argv[1:], "", options)
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+    
+    for o, a in opts:
+        if o == "--version":
+            version()
+            sys.exit()
+        else:
+            # Everything else maps straight to a config key. Just have
+            # to remove the "--" prefix from the option name.
+            config.__dict__[o[2:]] = a
+
+    # Print a usage message when not enough parameters are provided.
+    if not len(args) in (2,3):
+        sys.stderr.write("%s: incorrect number of arguments\n" % sys.argv[0])
+        usage();
+        sys.exit(2);
+
+    # If a project name was provided, override the default project name.
+    if len(args) == 3:
+        config.project = args[2]
+
+    # Go do the real work.
+    SvnClient(args[0], args[1], config).main()
+
+
+if __name__ == "__main__":
+    main()
+
+### The End ###


Property changes on: trunk/community/infrastructure/scripts/ciabot_svn.py
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/cronfile
===================================================================
--- trunk/community/infrastructure/scripts/cronfile	                        (rev 0)
+++ trunk/community/infrastructure/scripts/cronfile	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,15 @@
+#hanska-guest's crontab
+#m	h	dom	mon	dow	command
+
+BASE=/home/groups/debian-med/
+
+00	00	*	*	*	$BASE/scripts/update-bugs
+00	06	*	*	*	$BASE/scripts/update-bugs
+00	12	*	*	*	$BASE/scripts/update-bugs
+00	18	*	*	*	$BASE/scripts/update-bugs
+00	00	*	*	*	$BASE/scripts/update-ddtp
+00	00	*	*	*	$BASE/scripts/update-tasks-wrapper
+00	12	*	*	*	$BASE/scripts/update-tasks-wrapper
+*	*	*	*	*	$BASE/scripts/check-static
+
+00	*	*	*	*	PERL5LIB=$BASE/scripts/qa $BASE/scripts/qa/fetchdata -c $BASE/.debianqa/qa.conf

Added: trunk/community/infrastructure/scripts/debianqa.conf
===================================================================
--- trunk/community/infrastructure/scripts/debianqa.conf	                        (rev 0)
+++ trunk/community/infrastructure/scripts/debianqa.conf	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,51 @@
+# vim:syntax=dosini
+#
+# Sample config for DebianQA scripts
+#
+# A "~/" appearing at the beginning of a string will be replaced for the user's
+# home directory
+[qareport_cgi]
+templates_path = /home/groups/debian-med/scripts/qa/templates
+default_template = by_category
+group_name = Debian-Med
+group_url = http://debian-med.alioth.debian.org/
+wsvn_url = http://svn.debian.org/wsvn/debian-med/trunk/packages/%s/trunk/
+
+[svn]
+repository = file:///svn/debian-med
+packages_path = trunk/packages
+# path after the package name, should be the parent of the "debian/" directory
+post_path = trunk
+
+[archive]
+mirror = ftp://ftp.nl.debian.org/debian
+suites = unstable, testing, stable, oldstable, experimental
+sections = main, contrib, non-free
+suites_ttl = 360, 360, 10080, 10080, 360
+new_url = http://ftp-master.debian.org/new.html
+new_ttl = 60
+incoming_url = http://incoming.debian.org
+incoming_ttl = 60
+
+[watch] # Not implemented yet
+ttl = 360 # 6 hours
+use_cpan = 0
+cpan_mirror = ftp://cpan.org
+cpan_ttl = 360 # 6 hours
+
+[bts]
+ttl = 360 # 6 hours
+soap_proxy = http://bugs.debian.org/cgi-bin/soap.cgi
+soap_uri = Debbugs/SOAP
+# wontfix, pending, etch, sarge, etc
+ignore_keywords = etch, sarge, woody
+# wishlist, minor
+ignore_severities =
+
+# Parameters before any section header go into the [common] section
+[common]
+cache_dir = /home/groups/debian-med/.debianqa
+# verbosity level: error => 0, warn => 1, info => 2 debug => 3
+verbose = 1
+# Prepend syslog-style format?
+formatted_log => 1

Added: trunk/community/infrastructure/scripts/make-policy
===================================================================
--- trunk/community/infrastructure/scripts/make-policy	                        (rev 0)
+++ trunk/community/infrastructure/scripts/make-policy	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,28 @@
+#!/bin/bash
+BASE=/home/groups/debian-med
+DOCS=$BASE/htdocs/docs
+
+REPOS=$1
+REV=$2
+AUTH=$(svnlook author $REPOS -r $REV | sed 's/\&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g')
+
+sed -e "s/@REV@/$REV/" \
+    -e "s/@DATE@/`date -R`/" \
+        -e "s/@AUTHOR@/$AUTH/g" -i $DOCS/policy.tex
+
+PWD=$(pwd)
+cd $DOCS;
+
+# This is the PDF version
+pdflatex policy.tex
+
+# Now, let's do some magic to have HTML
+sed -e 's@\\includegraphics.*@@g' \
+	-e 's/dvips,//g' \
+	-e 's/pdftex,//g' \
+	policy.tex > policy-plain.tex
+
+rm -rf *.{aux,out,log}
+
+cd $PWD
+


Property changes on: trunk/community/infrastructure/scripts/make-policy
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/message.xml
===================================================================
--- trunk/community/infrastructure/scripts/message.xml	                        (rev 0)
+++ trunk/community/infrastructure/scripts/message.xml	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,47 @@
+<?xml version="1.0" ?>
+<message>
+	<timestamp>1197766746</timestamp>
+	<generator>
+		<name>Built-in Subversion repository interface</name>
+		<version>1.0</version>
+	</generator>
+	<source>
+		<project>Debian-Med</project>
+	</source>
+	<body>
+		<commit>
+			<revision>899</revision>
+			<author>hanska-guest</author>
+			<log>
+  * debian/patches - switching to quilt:
+    - DPATCH:
+      - 00list removed
+      - 01-fix_gcc4.3_warnings.dpatch removed
+      - 02-fix_CXXFLAGS.dpatch removed
+    - QUILT:
+      - series added
+      - 01-fix_gcc4.3_warnings.patch added
+      - 02-fix_CXXFLAGS.patch added
+      - 03-fix_FTBFS_gcc4.3_missing_includes.patch added (closes: #455269)
+  * debian/control - B-D updated, switching to quilt
+  * debian/rules:
+    - patch system updated, switching to quilt
+    - adding &quot;manpages&quot; and &quot;proda.1&quot; rules for 
+      easier generation of the manpage in the future
+  * debian/manpage.xml moved to debian/proda.1.xml</log>
+			<files>
+				<file action="remove">trunk/packages/proda/trunk/debian/patches/01-fix_gcc4.3_warnings.dpatch</file>
+				<file action="remove">trunk/packages/proda/trunk/debian/manpage.xml</file>
+				<file action="modify">trunk/packages/proda/trunk/debian/changelog</file>
+				<file action="add">trunk/packages/proda/trunk/debian/patches/02-fix_CXXFLAGS.patch</file>
+				<file action="modify">trunk/packages/proda/trunk/debian/rules</file>
+				<file action="remove">trunk/packages/proda/trunk/debian/patches/02-fix_CXXFLAGS.dpatch</file>
+				<file action="add">trunk/packages/proda/trunk/debian/patches/03-fix_FTBFS_gcc4.3_missing_includes.patch</file>
+				<file action="add">trunk/packages/proda/trunk/debian/patches/series</file>
+				<file action="remove">trunk/packages/proda/trunk/debian/patches/00list</file>
+				<file action="add">trunk/packages/proda/trunk/debian/patches/01-fix_gcc4.3_warnings.patch</file>
+				<file action="add">trunk/packages/proda/trunk/debian/proda.1.xml</file>
+			</files>
+		</commit>
+	</body>
+</message>

Added: trunk/community/infrastructure/scripts/parse_xml.py
===================================================================
--- trunk/community/infrastructure/scripts/parse_xml.py	                        (rev 0)
+++ trunk/community/infrastructure/scripts/parse_xml.py	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+
+#~ Copyright 2007 David Paleino <d.paleino at gmail.com>
+#~ 
+#~ This program is free software; you can redistribute it and/or modify
+#~ it under the terms of the GNU General Public License as published by
+#~ the Free Software Foundation; either version 3 of the License, or
+#~ (at your option) any later version.
+#~ 
+#~ This program is distributed in the hope that it will be useful,
+#~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+#~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#~ GNU General Public License for more details.
+#~ 
+#~ You should have received a copy of the GNU General Public License
+#~ along with this program; if not, write to the Free Software
+#~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#~ MA 02110-1301, USA.
+
+#~ This code heavily uses code from the CIA.vc project (http://cia.vc)
+#~  by Micah Dowty <micah at navi.cx>
+#~ The code of CIA.vc is free software, it is available under SVN
+#~  at http://cia-vc.googlecode.com/svn/trunk/
+
+from Nouvelle.Serial import Serializer
+from LibCIA import Formatters
+from LibCIA.Message import Message
+from LibCIA.Message import FormatterArgs
+from LibCIA.Web import RegexTransform
+
+# This has been customized
+#  -- David
+import Commit
+
+import getopt
+import sys
+import cgi
+import cgitb; cgitb.enable()
+
+### CIA.vc's /apps/stats/browser.py (adapted by David Paleino)
+
+def to_html():
+	"""Format any message as XHTML, using the LibCIA formatters and
+	Nouvelle serializer. This is used as a fallback for non-commit
+	messages.
+	"""
+	return serializer.render(formatter.formatMessage(msg))
+
+def format_log():
+	return serializer.render(commit.component_log(None, FormatterArgs(msg)))
+
+def format_files():
+	return serializer.render(commit.component_files(None, FormatterArgs(msg)))
+
+def format_headers():
+	return serializer.render(commit.component_headers(None, FormatterArgs(msg)))
+
+###
+
+serializer = Serializer()
+msg = Message(open("message.xml").read())
+formatter = Formatters.getFactory().findMedium('xhtml', msg)
+commit = Commit.CommitToXHTMLLong()
+hyperlinker = RegexTransform.AutoHyperlink()
+
+try:
+	(opts, args) = getopt.getopt(sys.argv[1:], '')
+except:
+	pass
+
+#print "Content-Type: application/xhtml+xml"
+print "Content-Type: text/html"
+print
+print """<html>
+<head>
+<title>SVN Commit Details</title>
+<link rel="stylesheet" href="style.css" />
+</head>
+<body>
+<h1>%s</h1>
+%s<hr />
+%s<hr />
+%s</body>""" % (args, format_headers(), format_log(), format_files())


Property changes on: trunk/community/infrastructure/scripts/parse_xml.py
___________________________________________________________________
Name: svn:executable
   + *

Copied: trunk/community/infrastructure/scripts/qa (from rev 1252, trunk/community/qa)

Added: trunk/community/infrastructure/scripts/update-bugs
===================================================================
--- trunk/community/infrastructure/scripts/update-bugs	                        (rev 0)
+++ trunk/community/infrastructure/scripts/update-bugs	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#
+# This Python script is:
+#  © 2007-2008, David Paleino <d.paleino at gmail.com>
+#
+# It is licensed under the terms of GNU General Public License (GPL) v3 or later.
+#
+
+import SOAPpy
+import HTMLTemplate
+import sys, os, re
+from datetime import datetime
+from email.Utils import formatdate
+import time
+from Tools import parseTasks
+
+url = "http://bugs.debian.org/cgi-bin/soap.cgi"
+base = "/var/lib/gforge/chroot/home/groups/debian-med"
+namespace = "Debbugs/SOAP"
+maint = "debian-med-packaging at lists.alioth.debian.org"
+tasks_repos = "svn://svn.debian.org/svn/cdd/projects/med/trunk/debian-med/tasks/"
+
+soap = SOAPpy.SOAPProxy(url, namespace)
+
+def getStatus(*args):
+	return soap.get_status(*args)
+
+def getBugs(*args):
+	"""
+		Possible keys are:
+		package, submitter, maint, src, severity, status, tag, owner, bugs
+		
+		To be called like getBugs(key, value), i.e.:
+		
+		getBugs("maint", "my at address.com")
+	"""
+	return soap.get_bugs(*args)
+
+def getField(bug, field):
+	return getStatus(bug).__getitem__('item').__getitem__('value').__getitem__(field)
+
+#<SOAPpy.Types.structType s-gensym3 at -1216426292>: {'item': 
+#<SOAPpy.Types.structType item at -1217077972>: {'value': 
+#<SOAPpy.Types.structType value at -1217031444>: {
+#'fixed_versions': [], 
+#'blockedby': '',
+#'keywords': 'unreproducible moreinfo',
+#'done': '"Nelson A. de Oliveira" <naoliv at gmail.com>',
+#'unarchived': '', 
+#'owner': '',
+#'id': 444343,
+#'subject': 'mummer: needs build dependency on libc headers and C++ compiler',
+#'forwarded': '',
+#'msgid': '<2007092721230716338.7665.reportbug at toblerone.sgn.cornell.edu>', 
+#'location': 'db-h',
+#'pending': 'done',
+#'found_date': [],
+#'originator': 'Robert Buels <rmb32 at cornell.edu>',
+#'blocks': '',
+#'tags': 'unreproducible moreinfo',
+#'date': 1190928242,
+#'mergedwith': '',
+#'severity': 'important',
+#'package': 'mummer',
+#'log_modified': 1191328025,
+#'fixed_date': [],
+#'found_versions': ['mummer/3.19-1'], 
+#'found': <SOAPpy.Types.structType found at -1216488532>: {'item': 
+#<SOAPpy.Types.structType item at -1216488340>: {'value': None, 'key': 
+#'mummer/3.19-1'}}, 'fixed': ''}, 'key': 444343}}
+
+def getSubject(bug):
+	return getField(bug, "subject")
+
+def getOriginator(bug):
+	return getField(bug, "originator")
+
+def getCloser(bug):
+	return getField(bug, "done")
+
+def getTags(bug):
+	return getField(bug, "tags")
+
+def getDate(bug):
+	return getField(bug, "date")
+
+def getSeverity(bug):
+	return getField(bug, "severity")
+
+def getPackage(bug):
+	return getField(bug, "package")
+
+def getAffectedVersions(bug):
+#	tmp = getField(bug, "found_versions")["data"]
+#	res = []
+#	for i in tmp:
+#		if type(i) != str:
+#			res.append("%s" % i)
+#		else:
+#			res.append(i)
+#	
+#	return res
+	return ["%s" % x for x in getField(bug, "found_versions")["data"]]
+
+def getFixedVersions(bug):
+#	tmp = getField(bug, "fixed_versions")["data"]
+#	res = []
+#	for i in tmp:
+#		if type(i) != str:
+#			res.append("%s" % i)
+#		else:
+#			res.append(i)
+#	
+#	return res
+	return ["%s" % x for x in getField(bug, "fixed_versions")["data"]]
+
+def getPending(bug):
+	return getField(bug, "pending")
+
+#
+# We're going to handle the HTML template here
+#
+
+# this is used later, for single-package pages
+pairs = []
+
+def renderTemplate(node, package, bugs):
+	global pairs
+	node.package.content = package
+	node.count.content = str(len(bugs))
+	
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+	#node.date.content = datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S %Z")
+	#Sat, 13 Oct 2007 10:39:27 +0200
+	
+	bugs.sort()
+	openbugs = {}
+	fixedbugs = {}
+	opencount = 0
+	fixedcount = 0
+	for tuple in bugs:
+		name, id = tuple
+		if getPending(id) != "done":
+			if name not in openbugs:
+				openbugs[name] = []
+			openbugs[name].append(id)
+			opencount += 1
+		else:
+			if name not in fixedbugs:
+				fixedbugs[name] = []
+			fixedbugs[name].append(id)
+			fixedcount += 1
+	
+	openlist = openbugs.items()
+	openlist.sort()
+	node.opencount.content = str(opencount)
+	
+	fixedlist = fixedbugs.items()
+	fixedlist.sort()
+	node.fixedcount.content = str(fixedcount)
+	
+	tmp = openlist + fixedlist
+	dict = {}
+	for tuple in tmp:
+		name, bugs = tuple
+		if name not in dict:
+			dict[name] = []
+		for bug in bugs:
+			dict[name].append(bug)
+	for item in dict:
+		pairs.append((item, dict[item]))
+	
+	node.openlist.repeat(renderPackage, openlist)
+	node.fixedlist.repeat(renderPackage, fixedlist, True)
+
+def renderPackage(node, buglist, showfixed = False):
+	name, bugs = buglist
+	node.name.content = name
+	node.count.content = str(len(bugs))
+	node.numbers.repeat(renderBugs, bugs, name, showfixed)
+	
+def renderBugs(node, number, package, showfixed):
+	#url = "bug_details.php?id=%s" % str(number)
+	url = "pkgs/%s.php#%s" % (package, str(number))
+	
+	severity = getSeverity(number)
+	subject = getSubject(number)
+	
+	node.id.atts["class"] = "bugid %s" % severity
+	node.id.raw = '<a href="%s">%s</a>' % (url, str(number))
+	node.summary.atts["class"] = "summary %s" % severity
+	node.summary.raw = '<a href="%s">%s</a>' % (url, subject)
+	node.severity.atts["class"] = "severity %s" % severity
+	node.severity.content = severity
+	
+	if showfixed:
+		node.fixed.atts["class"] = "fixed %s" % severity
+		value = "<br />".join(getFixedVersions(number)).replace("%s/" % package, "")
+		node.fixed.raw = value
+	
+#
+# These are the single-package pages method
+#
+
+def renderStatic(node, package, bugs):
+	node.package.content = package
+	node.count.content = str(len(bugs))
+	
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+	
+	node.allbugs.repeat(renderStaticBugs, bugs, package)
+
+def renderStaticBugs(node, bug, package):
+	node.id.content = str(bug)
+	node.id.atts["href"] = "http://bugs.debian.org/%s" % str(bug)
+	node.id.atts["name"] = str(bug)
+	fields = ["Subject",
+		"Sender",
+		"Tags",
+		"Date",
+		"Severity",
+		"Found in",
+		"Fixed in"]
+	node.singlebug.repeat(renderStaticBugDetails, fields, bug, package)
+
+def renderStaticBugDetails(node, field, bug, package):
+	node.field.raw = '<?=_("%s")?>' % field
+	if field == "Subject":
+		value = getSubject(bug)
+	elif field == "Sender":
+		value = getOriginator(bug)
+	elif field == "Tags":
+		value = getTags(bug)
+	elif field == "Date":
+		value = datetime.fromtimestamp(getDate(bug)).strftime("%c")
+	elif field == "Severity":
+		value = getSeverity(bug)
+	elif field == "Found in":
+		value = " ".join(getAffectedVersions(bug)).replace("%s/" % package, "")
+	elif field == "Fixed in":
+		value = " ".join(getFixedVersions(bug)).replace("%s/" % package, "")
+	
+	node.value.content = str(value).strip()
+
+#
+# Let's parse our task files (from Tools.py)
+#
+
+tasks_packages = parseTasks()
+
+#
+# Let's get our bugs
+#
+
+bugs = []
+for package in tasks_packages:
+	for bug in getBugs("package", package):
+		bugs.append((package, bug))
+
+#bugs = [("a",1),("b",2),("b",3),("c",0),("c",4),("a",67809),("c",1208)]
+
+#
+# Let's render bugs.php
+#
+
+f = open("%s/htdocs/bugs.tmpl" % base)
+tmpl = HTMLTemplate.Template(renderTemplate, f.read())
+f.close()
+
+package = "all"
+
+f = open("%s/static/bugs.php" % base, "w")
+f.write(tmpl.render(package, bugs))
+f.close()
+
+#
+# Let's do the <package>.php pages
+#
+
+for pair in pairs:
+	name, bugs = pair
+	outfile = "%s.php" % name
+	
+	f = open("%s/htdocs/bug_details.tmpl" % base)
+	tmpl = HTMLTemplate.Template(renderStatic, f.read())
+	f.close
+	
+	f = open("%s/static/pkgs/%s" % (base, outfile), "w")
+	f.write(tmpl.render(name, bugs))
+	f.close()


Property changes on: trunk/community/infrastructure/scripts/update-bugs
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/update-ddtp
===================================================================
--- trunk/community/infrastructure/scripts/update-ddtp	                        (rev 0)
+++ trunk/community/infrastructure/scripts/update-ddtp	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,199 @@
+#!/usr/bin/python
+
+#
+# This Python script is:
+#  (C) 2007, David Paleino <d.paleino at gmail.com>
+#
+# It is licensed under the terms of GNU General Public License (GPL)
+# v3, or any later revision.
+#
+
+from urllib import urlopen, urlretrieve
+import re
+import HTMLTemplate
+from datetime import datetime
+from email.Utils import formatdate
+import time
+from Tools import parseTasks, grep
+
+base = "/var/lib/gforge/chroot/home/groups/debian-med"
+#base = "/home/neo/tmp/debmed"
+ddtp_url = "http://ddtp.debian.net/debian/dists/sid/main/i18n/Translation-%s"
+fetch_url = "http://kleptog.org/cgi-bin/ddtss2-cgi/%s/fetch?package=%s"
+trans_url = "http://kleptog.org/cgi-bin/ddtss2-cgi/%s/translate/%s"
+
+# Should we dinamically get this list from PO translations
+# of the website?
+langs = ["de", "fr", "it", "pt_BR", "es", "ja"]
+langs.sort()
+
+dict = {}
+longs = {}
+shorts = {}
+
+#['adun.app', 'amap-align', 'amide', 'arb', 'bioimagesuite', 'bioperl', 'biosquid', 'blast2', 'boxshade', 'chemtool', 
+#'cimg-dev', 'clustalw', 'clustalw-mpi', 'clustalx', 'ctn', 'ctn-dev', 'ctsim', 'cycle', 'dcmtk', 'dialign', 
+#'dialign-t', 'dicomnifti', 'emboss', 'fastdnaml', 'fastlink', 'garlic', 'gdpc', 'gff2aplot', 'gff2ps', 'ghemical', 
+#'gnumed-client', 'gromacs', 'hmmer', 'imagej', 'kalign', 'libbio-ruby', 'libfslio0', 'libfslio0-dev', 
+#'libinsighttoolkit-dev', 'libmdc2-dev', 'libminc0-dev', 'libncbi6-dev', 'libniftiio0', 'libniftiio0-dev', 'loki', 
+#'mcl', 'medcon', 'melting', 'mencal', 'minc-tools', 'mipe', 'molphy', 'mummer', 'muscle', 'ncbi-epcr', 
+#'ncbi-tools-bin', 'ncbi-tools-x11', 'nifti-bin', 'njplot', 'octave', 'octave2.1', 'paw++', 'perlprimer', 'phylip', 
+#'poa', 'primer3', 'probcons', 'proda', 'pymol', 'python-biopython', 'python-nifti', 'r-base', 'r-base-core', 
+#'r-cran-qtl', 'rasmol', 'readseq', 'seaview', 'sibsim4', 'sigma-align', 'sim4', 't-coffee', 'tigr-glimmer', 
+#'tm-align', 'tree-ppuzzle', 'tree-puzzle', 'treetool', 'treeviewx', 'wise', 'xmedcon', 'zope-zms']
+
+def force_fetch(packages, lang = langs):
+	global langs
+	
+	# If it's a single language to be updated...
+	#
+	# NOT USED CURRENTLY.
+	if type(lang) == "str":
+		for name in packages:
+			urlopen(fetch_url % (lang, name))
+	else:
+		for lang_name in lang:
+			for pkg_name in packages:
+				#print "Fetching %s - %s" % (lang_name, pkg_name)
+				urlopen(fetch_url % (lang_name, pkg_name))
+
+def get_status(package):
+	global dict
+	global langs
+	status = {}
+	
+	for lang in langs:
+#		print "Parsing %s - %s" % (package, lang)
+		if grep("Package: %s\n" % package, "%s/data/ddtp/Translation-%s" % (base, lang)):
+			status[lang] = 1
+		else:
+			status[lang] = 0
+	
+	dict[package] = status
+
+def parse_description(package):
+	global dict
+	global longs
+	global shorts
+	
+	tmp_long = {}
+	tmp_short = {}
+	for lang in langs:
+		if dict[package][lang]:
+			f = open("%s/data/ddtp/Translation-%s" % (base, lang), "r")
+			# Package: 9wm\nDescription-md5: (?P<md5sum>.{32})\nDescription-\w+: (?P<short>.*)\n(?P<long>.*\n)\n
+			# TODO: Fix the regex. (it worked in Kodos :()
+			regex = r"""Package: %s
+Description-md5: (?P<md5sum>.{32})
+Description-\w+: (?P<short>.*)
+(?P<long>(^ .*$\n)+\n?)""" % re.escape(package)
+
+			p = re.compile(regex, re.MULTILINE)
+			m = p.search(f.read())
+			if m:
+				tmp_long[lang] = m.group("long").replace("\n ", "<br />").replace("<br />.<br />", "<br /><br />")
+				tmp_short[lang] = m.group("short")
+			f.close()
+		
+	longs[package] = tmp_long
+	shorts[package] = tmp_short
+	
+def renderTemplate(node, langs, statuses):
+	node.langs.repeat(renderLangs, langs)
+	
+	names = statuses.keys()
+	names.sort()
+	
+	node.packages.repeat(renderStatuses, names, statuses)
+	
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+
+def renderLangs(node, lang):
+	node.code.raw = "<img src=\"/img/langs/%s.png\" alt=\"%s\" />" % (lang, lang)
+
+def renderStatuses(node, package, status):
+	node.name.raw = '<a href="/ddtp/%s.php">%s</a>' % (package, package)
+	
+	langs = dict[package].keys()
+	langs.sort()
+
+	node.translations.repeat(renderTrans, langs, package, dict[package])
+
+def renderTrans(node, lang, package, status):
+	if status[lang] == 0:
+		node.status.raw = '<a href="%s"><img src="/img/no.png" alt="no" title="%s - <?=_("translation not available")?>" /></a>' % (trans_url % (lang, package), lang)
+	else:
+		node.status.raw = '<a href="%s"><img src="/img/ok.png" alt="<?=_("yes")?>" title="%s - <?=_("translated")?>" /></a><a href="%s"><img src="/img/go.png" alt="<?=_("edit")?>" title="%s - <?=_("edit translation")?>" /></a>' % ("/ddtp/%s.php" % package, lang, trans_url % (lang, package), lang)
+
+#tem:
+#	con:package
+#	rep:langs
+#		con:name
+#		con:short
+#		con:long                                                               
+def renderStatic(node, package):
+	node.package.content = package
+	node.langs.repeat(renderSingleTrans, dict[package], package)
+	
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+
+def renderSingleTrans(node, lang, package):
+	node.name.raw = '<img src="/img/langs/%s.png" alt="%s" title="%s"/>' % (lang, lang, lang)
+	if dict[package][lang]:
+#		print "Lang: %s" % lang
+#		print "Package: %s" % package
+#		print "Dict: %s" % dict[package][lang]
+#		print "Short: %s" % shorts[package]
+#		print "Long: %s" % longs[package]
+		node.short.raw = shorts[package][lang]
+		node.long.raw = longs[package][lang]
+	else:
+		node.short.raw = '<?=_("untranslated")?>'
+		node.long.raw = '<?=_("untranslated")?><br /><?=_("Please follow the link below to start translating")?>:<br /><br /><a href="%s">%s</a>' % (trans_url % (lang, package), trans_url % (lang, package))
+	
+for lang in langs:
+	url = ddtp_url % lang
+	pos = url.rfind("/")
+	name = "%s/data/ddtp/%s" % (base, url[pos + 1:])
+	urlretrieve(url, name)
+	
+packages = parseTasks()
+#packages = ["adun.app", "gcc", "emboss-explorer"]
+packages.sort()
+
+# XXX TODO HACK NEWS
+# Re-enable this to force-fetch the packages.
+# Shouldn't be done unless having talked to DDTSS admins.
+#  -- David
+#force_fetch(packages)
+for item in packages:
+	get_status(item)
+	parse_description(item)
+
+#print dict
+#print longs
+#print shorts
+
+# Let's generate the overview page
+f = open("%s/htdocs/ddtp.tmpl" % base)
+tmpl = HTMLTemplate.Template(renderTemplate, f.read())
+f.close()
+
+f = open("%s/static/ddtp.php" % base, "w")
+f.write(tmpl.render(langs, dict))
+f.close()
+
+# Let's generate nice static overview per-package pages
+for name in packages:
+	outfile = "%s/static/ddtp/%s.php" % (base, name)
+	f = open("%s/htdocs/ddtp_details.tmpl" % base)
+	tmpl = HTMLTemplate.Template(renderStatic, f.read())
+	f.close()
+	
+#	print tmpl.structure()
+	
+	f = open(outfile, "w")
+	f.write(tmpl.render(name))
+	f.close()


Property changes on: trunk/community/infrastructure/scripts/update-ddtp
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/update-tasks
===================================================================
--- trunk/community/infrastructure/scripts/update-tasks	                        (rev 0)
+++ trunk/community/infrastructure/scripts/update-tasks	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,319 @@
+#!/usr/bin/python -W ignore
+
+#
+# This Python script is:
+#  (C) 2007, David Paleino <d.paleino at gmail.com>
+#
+# It is licensed under the terms of GNU General Public License (GPL)
+# v3, or any later revision.
+#
+
+import apt
+import apt_pkg
+import apt_inst
+import HTMLTemplate
+import re
+import sys
+import time
+from datetime import datetime
+from email.Utils import formatdate
+from Tools import *
+
+base = "/var/lib/gforge/chroot/home/groups/debian-med"
+tasks = "%s/scripts/tasks" % base
+
+official = {}	# Official packages
+todo = {}		# Packages not in repositories, nor unofficial,
+				# nor prospected. They will eventually go into
+				# "unavailable".
+det = {}		# Official Packages details
+
+# let's get our nice dict in the form:
+# { 'task_foo': ['package1', 'package2', '...'],
+#   'task_bar': ['package3', 'package4', '...']}
+
+packages = parseTasks(None, True)
+unofficial = parseTasksNonOff()
+unavailable = parseTasksUnavail()
+task_details = parseTaskDetails()
+
+tasks = packages.keys()
+tasks.sort()
+
+apt_pkg.init()
+#~ apt_pkg.Config.Set("APT::Acquire::Translation", "it")
+
+cache = apt_pkg.GetCache()
+depcache = apt_pkg.GetDepCache(cache)
+aptcache = apt.Cache()
+
+###
+# Wrappers around apt_pkg
+###
+
+def __getRecords(package):
+	### TODO: convert to Python API
+	(f, index) = depcache.GetCandidateVer(cache[package]).TranslatedDescription.FileList.pop(0)
+	records = apt_pkg.GetPkgRecords(cache)
+	records.Lookup ((f, index))
+	return records
+
+def __getDesc(package):
+	regex = r"""(?P<short>.*)
+(?P<long>(^ .*$\n)+\n?)"""
+
+	p = re.compile(regex, re.MULTILINE)
+	m = p.match(getSections(package)['Description'])
+	if m:
+		return {'ShortDesc': m.group("short"), 'LongDesc': m.group("long")}
+	else:
+		return False
+
+def getShort(package):
+	### TODO: convert to Python API
+	desc = __getDesc(package)
+	if desc:
+		return desc['ShortDesc']
+	else:
+		# Fallback to the C++ wrapper
+		return __getRecords(package).ShortDesc
+
+def getLong(package):
+	### TODO: convert to Python API
+	desc = __getDesc(package)
+	if desc:
+		return desc['LongDesc']
+	else:
+		# Fallback to the C++ wrapper
+		return __getRecords(package).LongDesc
+
+def getHomepage(package):
+	sect = getSections(package)
+	try:
+		return sect['Homepage']
+	# Fallback to the old "  Homepage: ..." pseudo-field
+	# TODO: also renders _wrong_ "URL" pseudo-fields! Fix the packages!
+	except:
+		p = re.compile(".*(?P<field>Homepage|URL): (?P<url>.*)", re.DOTALL)
+		m = p.match(getLong(package))
+		if m:
+			tmp = det[package]['LongDesc']
+			det[package]['LongDesc'] = tmp.replace(m.group("field") + ": " + m.group("url"), "")
+			return m.group("url")
+		else:
+			# We don't have any valid field for homepage,
+			# return a suitable "#" for <a href>s.
+			return "#"
+
+def getDebUrl(package):
+	try:
+		return getSections(package)["Filename"]
+	except:
+		return "#"
+
+def getVersion(package):
+	try:
+		return getSections(package)["Version"]
+	except:
+		# Fallback to the C++ wrapper
+		for pkg in cache.Packages:
+			if pkg.Name in det:
+				if not pkg.VersionList:
+					return "N/A"
+				else:
+					return pkg.VersionList[0].VerStr
+
+def getLicense(package):
+	### FIX
+	return "GPL-foo"
+
+def getSections(package):
+	pkg = aptcache[package]
+	pkg._lookupRecord(True)
+	return apt_pkg.ParseSection(pkg._records.Record)
+
+###
+# Template handlers
+###
+
+def renderIndex(node, tasks):
+	node.tasks.repeat(renderTaskList, tasks)
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+
+def renderTaskList(node, task):
+	node.task.raw = """<a href="/tasks/%s.php" name="%s" id="%s">%s</a>""" % (task, task, task, task.capitalize())
+
+def renderTasks(node, task, packages, details):
+	node.task.content = details['Task']
+	node.shortdesc.content = details['ShortDesc']
+	node.heading.content = details['ShortDesc']
+	node.longdesc.content = details['LongDesc']
+
+	t = datetime.now()
+	node.date.content = formatdate(time.mktime(t.timetuple()))
+
+	# Let's separate official packages from others
+	for pkg in packages:
+		# If the package has a short description in cache,
+		# there's an high chance it is an official package.
+		# Probably we can use a better algorithm? (I believe
+		# Alioth's APT cache won't be contaminated by external
+		# repositories)
+		try:
+			short = getShort(pkg)
+			if not task in official:
+				official[task] = []
+			official[task].append(pkg)
+			det[pkg] = {}
+			det[pkg]['ShortDesc'] = short
+			det[pkg]['LongDesc'] = getLong(pkg).replace("%s\n" % short, "")
+			det[pkg]['LongDesc'] = det[pkg]['LongDesc'].replace("<", "&lt;").replace(">", "&gt;")
+			# getHomepage() does some magic on ['LongDesc']
+			det[pkg]['Homepage'] = getHomepage(pkg)
+			det[pkg]['LongDesc'] = det[pkg]['LongDesc'].replace("\n .\n", "<br /><br />").replace("\n", "")
+			det[pkg]['Version'] = getVersion(pkg)
+			det[pkg]['License'] = getLicense(pkg)
+			det[pkg]['Task'] = task
+			det[pkg]['Pkg-URL'] = "http://packages.debian.org/unstable/%s/%s" % (getSections(pkg)['Section'], pkg)
+			### BUG: some packages don't get the right Filename
+			###      see, for example, "treeviewx": it has a Filename
+			###      field, but doesn't get parsed.
+			### FIX: installed packages with versions newer than the
+			###      one in repositories don't have that field. :@!
+			det[pkg]['Deb-URL'] = "http://ftp.debian.org/%s" % getDebUrl(pkg)
+		except:
+			pass
+
+	if task in official:
+		node.official_head.raw = """<h2>
+<a id="official-debs" name="official-debs"></a>
+	Official Debian packages
+</h2>"""
+		node.official.repeat(renderOfficial, official[task], task)
+
+	if task in todo:
+		error = True
+	else:
+		error = False
+
+	if task in unofficial:
+		node.unofficial_head.raw = """<h2>
+<a id="inofficial-debs" name="inofficial-debs"></a>
+	Inofficial Debian packages
+</h2>"""
+		node.unofficial.repeat(renderUnofficial, unofficial[task])
+		error = False
+
+	if task in unavailable:
+		node.unavailable_head.raw = """<h2>
+<a id="debs-not-available" name="debs-not-available"></a>
+	Debian packages not available
+</h2>"""
+		node.unavailable.repeat(renderUnavailable, unavailable[task])
+		error = False
+
+	if error:
+		# The package probably needs a proper prospective entry in the
+		# task files. Write it to stdout.
+		print "Error: problems with %s" % task
+
+def renderOfficial(node, package, task):
+	# Here we parse just official packages
+	node.shortdesc.content = det[package]['ShortDesc']
+	node.project.raw = "<table class=\"project\" summary=\"%s\">" % package
+	node.anchor.atts['name'] = package
+	node.anchor.atts['id'] = package
+	node.name.content = package.capitalize()
+	node.url.atts['href'] = det[package]['Homepage']
+	if det[package]['Homepage'] == "#":
+		node.url.content = "Homepage not available"
+	else:
+		node.url.content = det[package]['Homepage']
+
+	node.longdesc.raw = det[package]['LongDesc']
+	node.version.content = "Version: %s" % det[package]['Version']
+	node.license.content = "License: %s" % det[package]['License']
+	node.pkgurl.atts['href'] = det[package]['Pkg-URL']
+	node.pkgurl.content = "Official Debian package"
+	node.deburl.atts['href'] = det[package]['Deb-URL']
+	#~ node.deburl.content = "X" ### TODO: add a nice icon here to download the .deb package
+	node.deburl.raw = "<img src=\"/img/deb-icon.png\" />"
+
+def renderUnofficial(node, package):
+	# Here we parse just unofficial packages
+	node.shortdesc.content = package['ShortDesc']
+	node.longdesc.raw = package['LongDesc']
+	node.project.raw = "<table class=\"project\" summary=\"%s\">" % package['Package']
+	node.anchor.atts['name'] = package['Package']
+	node.anchor.atts['id'] = package['Package']
+	node.name.content = package['Package'].capitalize()
+	node.url.atts['href'] = package['Homepage']
+	node.url.content = package['Homepage']
+	node.license.content = "License: %s" %package['License']
+	node.pkgurl.atts['href'] = package['Pkg-URL']
+	node.pkgurl.content = "Inofficial Debian package"
+
+	# Let's try to get the version from the package name
+	# (following Debian standards: <name>_<ver>_<arch>.deb)
+	regex = ".*/%s_(?P<version>.*)_.*\.deb$" % package['Package']
+	p = re.compile(regex)
+	m = p.search(package['Pkg-URL'])
+	if m:
+		node.version.content = "Version: %s" % m.group("version")
+	else:
+		node.version.content = "Version: N/A"
+
+
+def renderUnavailable(node, package):
+	# Parsing unavailable packages :(
+	# PACKAGE THEM! :)
+	name = package['Package']
+	if package['ShortDesc']:
+		node.shortdesc.content = package['ShortDesc']
+	else:
+		node.shortdesc.content = "N/A"
+	node.longdesc.raw = package['LongDesc']
+	node.project.raw = "<table class=\"project\" summary=\"%s\">" % name
+	if package['Responsible']:
+		node.responsible.content = package['Responsible']
+	else:
+		node.responsible.raw = "no one"
+	if package['WNPP']:
+		node.wnpp.raw = " &mdash; <a href=\"http://bugs.debian.org/%s\">wnpp</a>" % package['WNPP']
+	node.anchor.atts['name'] = name
+	node.anchor.atts['id'] = name
+	node.name.content = name.capitalize()
+	if package['Homepage']:
+		node.url.atts['href'] = package['Homepage']
+		node.url.content = package['Homepage']
+	else:
+		node.url.atts['href'] = "#"
+		node.url.content = "N/A"
+	if package['License']:
+		node.license.raw = "<?=_('License')?>: %s" % package['License']
+	else:
+		node.license.raw = "<?=_('License')?>: N/A"
+
+# Let's render the Tasks Page index, first
+f = open("%s/htdocs/tasks_idx.tmpl" % base)
+tmpl = HTMLTemplate.Template(renderIndex, f.read())
+f.close()
+f = open("%s/static/tasks/index.php" % base, "w")
+f.write(tmpl.render(tasks))
+f.close()
+
+# Let's render single pages now.
+f = open("%s/htdocs/tasks.tmpl" % base)
+tmpl = HTMLTemplate.Template(renderTasks, f.read())
+f.close()
+
+for task in tasks:
+	f = open("%s/static/tasks/%s.php" % (base, task), "w")
+
+	# This is to avoid useless <br>eaks before closing the cell
+	source = tmpl.render(task, packages[task], task_details[task])
+	f.write(re.sub(r"<br /><br />[ ]*</td>", "</td>", source))
+
+	f.close()
+


Property changes on: trunk/community/infrastructure/scripts/update-tasks
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/update-tasks-wrapper
===================================================================
--- trunk/community/infrastructure/scripts/update-tasks-wrapper	                        (rev 0)
+++ trunk/community/infrastructure/scripts/update-tasks-wrapper	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,4 @@
+#!/bin/bash
+SCRIPT=/home/groups/debian-med/scripts/update-tasks
+
+python $SCRIPT 2>/dev/null


Property changes on: trunk/community/infrastructure/scripts/update-tasks-wrapper
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/community/infrastructure/scripts/update-website
===================================================================
--- trunk/community/infrastructure/scripts/update-website	                        (rev 0)
+++ trunk/community/infrastructure/scripts/update-website	2008-02-02 12:12:23 UTC (rev 1258)
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+umask 002
+
+REPOS=$1
+REV=$2
+URL_REPOS="svn://svn.debian.org/svn/debian-med/trunk/community/website/"
+BASE=/var/lib/gforge/chroot/home/groups/debian-med
+
+echo "Updating website... rev. $REV"
+mv $BASE/htdocs/* $BASE/htdocs-backup/ && rm -rf $BASE/htdocs/*
+svn --force export $URL_REPOS $BASE/htdocs/
+[ "x$?" == "x0" ] && rm -rf $BASE/htdocs-backup/*
+
+# let's substitute our variables...
+AUTH=$(svnlook author $REPOS -r $REV | sed 's/\&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g')
+
+sed -e "s/#REV#/$REV/" \
+    -e "s/#DATE#/`date -uR`/" \
+    -e "s/#AUTHOR#/$AUTH/g" -i $BASE/htdocs/inc/footer.inc.php
+
+# Auto-compile .po files
+LOCALES="$BASE/htdocs/locale"
+
+for lang in $(find $LOCALES -mindepth 1 -maxdepth 1 -type d)
+do
+	msgfmt $lang/LC_MESSAGES/messages.po && mv messages.mo $lang/LC_MESSAGES/
+done


Property changes on: trunk/community/infrastructure/scripts/update-website
___________________________________________________________________
Name: svn:executable
   + *

Deleted: trunk/community/infrastructure/update-bugs
===================================================================
--- trunk/community/infrastructure/update-bugs	2008-02-02 11:03:47 UTC (rev 1257)
+++ trunk/community/infrastructure/update-bugs	2008-02-02 12:12:23 UTC (rev 1258)
@@ -1,276 +0,0 @@
-#!/usr/bin/python
-
-#
-# This Python script is:
-#  (C) 2007, David Paleino <d.paleino at gmail.com>
-#
-# It is licensed under the terms of GNU General Public License (GPL) v2 or later.
-#
-
-import SOAPpy
-import HTMLTemplate
-import os
-import re
-from datetime import datetime
-from email.Utils import formatdate
-import time
-from Tools import parseTasks
-
-url = "http://bugs.debian.org/cgi-bin/soap.cgi"
-base = "/var/lib/gforge/chroot/home/groups/debian-med"
-namespace = "Debbugs/SOAP"
-maint = "debian-med-packaging at lists.alioth.debian.org"
-tasks_repos = "svn://svn.debian.org/svn/cdd/projects/med/trunk/debian-med/tasks/"
-
-soap = SOAPpy.SOAPProxy(url, namespace)
-
-def getStatus(*args):
-	return soap.get_status(*args)
-
-def getBugs(*args):
-	"""
-		Possible keys are:
-		package, submitter, maint, src, severity, status, tag, owner, bugs
-		
-		To be called like getBugs(key, value), i.e.:
-		
-		getBugs("maint", "my at address.com")
-	"""
-	return soap.get_bugs(*args)
-
-def getField(bug, field):
-	return getStatus(bug).__getitem__('item').__getitem__('value').__getitem__(field)
-
-#<SOAPpy.Types.structType s-gensym3 at -1216426292>: {'item': 
-#<SOAPpy.Types.structType item at -1217077972>: {'value': 
-#<SOAPpy.Types.structType value at -1217031444>: {
-#'fixed_versions': [], 
-#'blockedby': '',
-#'keywords': 'unreproducible moreinfo',
-#'done': '"Nelson A. de Oliveira" <naoliv at gmail.com>',
-#'unarchived': '', 
-#'owner': '',
-#'id': 444343,
-#'subject': 'mummer: needs build dependency on libc headers and C++ compiler',
-#'forwarded': '',
-#'msgid': '<2007092721230716338.7665.reportbug at toblerone.sgn.cornell.edu>', 
-#'location': 'db-h',
-#'pending': 'done',
-#'found_date': [],
-#'originator': 'Robert Buels <rmb32 at cornell.edu>',
-#'blocks': '',
-#'tags': 'unreproducible moreinfo',
-#'date': 1190928242,
-#'mergedwith': '',
-#'severity': 'important',
-#'package': 'mummer',
-#'log_modified': 1191328025,
-#'fixed_date': [],
-#'found_versions': ['mummer/3.19-1'], 
-#'found': <SOAPpy.Types.structType found at -1216488532>: {'item': 
-#<SOAPpy.Types.structType item at -1216488340>: {'value': None, 'key': 
-#'mummer/3.19-1'}}, 'fixed': ''}, 'key': 444343}}
-
-def getSubject(bug):
-	return getField(bug, "subject")
-
-def getOriginator(bug):
-	return getField(bug, "originator")
-
-def getCloser(bug):
-	return getField(bug, "done")
-
-def getTags(bug):
-	return getField(bug, "tags")
-
-def getDate(bug):
-	return getField(bug, "date")
-
-def getSeverity(bug):
-	return getField(bug, "severity")
-
-def getPackage(bug):
-	return getField(bug, "package")
-
-def getAffectedVersions(bug):
-	return getField(bug, "found_versions")["data"]
-
-def getFixedVersions(bug):
-	return getField(bug, "fixed_versions")["data"]
-
-def getPending(bug):
-	return getField(bug, "pending")
-
-#
-# We're going to handle the HTML template here
-#
-
-# this is used later, for single-package pages
-pairs = []
-
-def renderTemplate(node, package, bugs):
-	global pairs
-	node.package.content = package
-	node.count.content = str(len(bugs))
-	
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-	#node.date.content = datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S %Z")
-	#Sat, 13 Oct 2007 10:39:27 +0200
-	
-	bugs.sort()
-	openbugs = {}
-	fixedbugs = {}
-	opencount = 0
-	fixedcount = 0
-	for tuple in bugs:
-		name, id = tuple
-		if getPending(id) != "done":
-			if name not in openbugs:
-				openbugs[name] = []
-			openbugs[name].append(id)
-			opencount += 1
-		else:
-			if name not in fixedbugs:
-				fixedbugs[name] = []
-			fixedbugs[name].append(id)
-			fixedcount += 1
-	
-	openlist = openbugs.items()
-	openlist.sort()
-	node.opencount.content = str(opencount)
-	
-	fixedlist = fixedbugs.items()
-	fixedlist.sort()
-	node.fixedcount.content = str(fixedcount)
-	
-	tmp = openlist + fixedlist
-	dict = {}
-	for tuple in tmp:
-		name, bugs = tuple
-		if name not in dict:
-			dict[name] = []
-		for bug in bugs:
-			dict[name].append(bug)
-	for item in dict:
-		pairs.append((item, dict[item]))
-	
-	node.openlist.repeat(renderPackage, openlist)
-	node.fixedlist.repeat(renderPackage, fixedlist, True)
-
-def renderPackage(node, buglist, showfixed = False):
-	name, bugs = buglist
-	node.name.content = name
-	node.count.content = str(len(bugs))
-	node.numbers.repeat(renderBugs, bugs, name, showfixed)
-	
-def renderBugs(node, number, package, showfixed):
-	#url = "bug_details.php?id=%s" % str(number)
-	url = "pkgs/%s.php#%s" % (package, str(number))
-	
-	severity = getSeverity(number)
-	subject = getSubject(number)
-	
-	node.id.atts["class"] = "bugid %s" % severity
-	node.id.raw = '<a href="%s">%s</a>' % (url, str(number))
-	node.summary.atts["class"] = "summary %s" % severity
-	node.summary.raw = '<a href="%s">%s</a>' % (url, subject)
-	node.severity.atts["class"] = "severity %s" % severity
-	node.severity.content = severity
-	
-	if showfixed:
-		node.fixed.atts["class"] = "fixed %s" % severity
-		value = "<br />".join(getFixedVersions(number)).replace("%s/" % package, "")
-		node.fixed.raw = value
-	
-#
-# These are the single-package pages method
-#
-
-def renderStatic(node, package, bugs):
-	node.package.content = package
-	node.count.content = str(len(bugs))
-	
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-	
-	node.allbugs.repeat(renderStaticBugs, bugs, package)
-
-def renderStaticBugs(node, bug, package):
-	node.id.content = str(bug)
-	node.id.atts["href"] = "http://bugs.debian.org/%s" % str(bug)
-	node.id.atts["name"] = str(bug)
-	fields = ["Subject",
-		"Sender",
-		"Tags",
-		"Date",
-		"Severity",
-		"Found in",
-		"Fixed in"]
-	node.singlebug.repeat(renderStaticBugDetails, fields, bug, package)
-
-def renderStaticBugDetails(node, field, bug, package):
-	node.field.raw = '<?=_("%s")?>' % field
-	if field == "Subject":
-		value = getSubject(bug)
-	elif field == "Sender":
-		value = getOriginator(bug)
-	elif field == "Tags":
-		value = getTags(bug)
-	elif field == "Date":
-		value = datetime.fromtimestamp(getDate(bug)).strftime("%c")
-	elif field == "Severity":
-		value = getSeverity(bug)
-	elif field == "Found in":
-		value = " ".join(getAffectedVersions(bug)).replace("%s/" % package, "")
-	elif field == "Fixed in":
-		value = " ".join(getFixedVersions(bug)).replace("%s/" % package, "")
-	
-	node.value.content = str(value).strip()
-
-#
-# Let's parse our task files (from Tools.py)
-#
-
-tasks_packages = parseTasks()
-
-#
-# Let's get our bugs
-#
-
-bugs = []
-for package in tasks_packages:
-	for bug in getBugs("package", package):
-		bugs.append((package, bug))
-
-#bugs = [("a",1),("b",2),("b",3),("c",0),("c",4),("a",67809),("c",1208)]
-
-#
-# Let's render bugs.php
-#
-
-f = open("%s/htdocs/bugs.tmpl" % base)
-tmpl = HTMLTemplate.Template(renderTemplate, f.read())
-f.close()
-
-package = "all"
-
-f = open("%s/static/bugs.php" % base, "w")
-f.write(tmpl.render(package, bugs))
-f.close()
-
-#
-# Let's do the <package>.php pages
-#
-
-for pair in pairs:
-	name, bugs = pair
-	outfile = "%s.php" % name
-	
-	f = open("%s/htdocs/bug_details.tmpl" % base)
-	tmpl = HTMLTemplate.Template(renderStatic, f.read())
-	f.close
-	
-	f = open("%s/static/pkgs/%s" % (base, outfile), "w")
-	f.write(tmpl.render(name, bugs))
-	f.close()

Deleted: trunk/community/infrastructure/update-ddtp
===================================================================
--- trunk/community/infrastructure/update-ddtp	2008-02-02 11:03:47 UTC (rev 1257)
+++ trunk/community/infrastructure/update-ddtp	2008-02-02 12:12:23 UTC (rev 1258)
@@ -1,194 +0,0 @@
-#!/usr/bin/python
-
-#
-# This Python script is:
-#  (C) 2007, David Paleino <d.paleino at gmail.com>
-#
-# It is licensed under the terms of GNU General Public License (GPL)
-# v3, or any later revision.
-#
-
-from urllib import urlopen, urlretrieve
-import re
-import HTMLTemplate
-from datetime import datetime
-from email.Utils import formatdate
-import time
-from Tools import parseTasks, grep
-
-base = "/var/lib/gforge/chroot/home/groups/debian-med"
-#base = "/home/neo/tmp/debmed"
-ddtp_url = "http://ddtp.debian.net/debian/dists/sid/main/i18n/Translation-%s"
-fetch_url = "http://kleptog.org/cgi-bin/ddtss2-cgi/%s/fetch?package=%s"
-trans_url = "http://kleptog.org/cgi-bin/ddtss2-cgi/%s/translate/%s"
-
-# Should we dinamically get this list from PO translations
-# of the website?
-langs = ["de", "fr", "it", "pt_BR", "es", "ja"]
-langs.sort()
-
-dict = {}
-longs = {}
-shorts = {}
-
-#['adun.app', 'amap-align', 'amide', 'arb', 'bioimagesuite', 'bioperl', 'biosquid', 'blast2', 'boxshade', 'chemtool', 
-#'cimg-dev', 'clustalw', 'clustalw-mpi', 'clustalx', 'ctn', 'ctn-dev', 'ctsim', 'cycle', 'dcmtk', 'dialign', 
-#'dialign-t', 'dicomnifti', 'emboss', 'fastdnaml', 'fastlink', 'garlic', 'gdpc', 'gff2aplot', 'gff2ps', 'ghemical', 
-#'gnumed-client', 'gromacs', 'hmmer', 'imagej', 'kalign', 'libbio-ruby', 'libfslio0', 'libfslio0-dev', 
-#'libinsighttoolkit-dev', 'libmdc2-dev', 'libminc0-dev', 'libncbi6-dev', 'libniftiio0', 'libniftiio0-dev', 'loki', 
-#'mcl', 'medcon', 'melting', 'mencal', 'minc-tools', 'mipe', 'molphy', 'mummer', 'muscle', 'ncbi-epcr', 
-#'ncbi-tools-bin', 'ncbi-tools-x11', 'nifti-bin', 'njplot', 'octave', 'octave2.1', 'paw++', 'perlprimer', 'phylip', 
-#'poa', 'primer3', 'probcons', 'proda', 'pymol', 'python-biopython', 'python-nifti', 'r-base', 'r-base-core', 
-#'r-cran-qtl', 'rasmol', 'readseq', 'seaview', 'sibsim4', 'sigma-align', 'sim4', 't-coffee', 'tigr-glimmer', 
-#'tm-align', 'tree-ppuzzle', 'tree-puzzle', 'treetool', 'treeviewx', 'wise', 'xmedcon', 'zope-zms']
-
-def force_fetch(packages, lang = langs):
-	global langs
-	
-	# If it's a single language to be updated...
-	#
-	# NOT USED CURRENTLY.
-	if type(lang) == "str":
-		for name in packages:
-			urlopen(fetch_url % (lang, name))
-	else:
-		for lang_name in lang:
-			for pkg_name in packages:
-				#print "Fetching %s - %s" % (lang_name, pkg_name)
-				urlopen(fetch_url % (lang_name, pkg_name))
-
-def get_status(package):
-	global dict
-	global langs
-	status = {}
-	
-	for lang in langs:
-#		print "Parsing %s - %s" % (package, lang)
-		if grep("Package: %s\n" % package, "%s/data/ddtp/Translation-%s" % (base, lang)):
-			status[lang] = 1
-		else:
-			status[lang] = 0
-	
-	dict[package] = status
-
-def parse_description(package):
-	global dict
-	global longs
-	global shorts
-	
-	tmp_long = {}
-	tmp_short = {}
-	for lang in langs:
-		if dict[package][lang]:
-			f = open("%s/data/ddtp/Translation-%s" % (base, lang), "r")
-			# Package: 9wm\nDescription-md5: (?P<md5sum>.{32})\nDescription-\w+: (?P<short>.*)\n(?P<long>.*\n)\n
-			# TODO: Fix the regex. (it worked in Kodos :()
-			regex = r"""Package: %s
-Description-md5: (?P<md5sum>.{32})
-Description-\w+: (?P<short>.*)
-(?P<long>(^ .*$\n)+\n?)""" % package
-
-			p = re.compile(regex, re.MULTILINE)
-			m = p.search(f.read())
-			if m:
-				tmp_long[lang] = m.group("long").replace("\n ", "<br />").replace("<br />.<br />", "<br /><br />")
-				tmp_short[lang] = m.group("short")
-			f.close()
-		
-	longs[package] = tmp_long
-	shorts[package] = tmp_short
-	
-def renderTemplate(node, langs, statuses):
-	node.langs.repeat(renderLangs, langs)
-	
-	names = statuses.keys()
-	names.sort()
-	
-	node.packages.repeat(renderStatuses, names, statuses)
-	
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-
-def renderLangs(node, lang):
-	node.code.raw = "<img src=\"/img/langs/%s.png\" alt=\"%s\" />" % (lang, lang)
-
-def renderStatuses(node, package, status):
-	node.name.raw = '<a href="/ddtp/%s.php">%s</a>' % (package, package)
-	
-	langs = dict[package].keys()
-	langs.sort()
-
-	node.translations.repeat(renderTrans, langs, package, dict[package])
-
-def renderTrans(node, lang, package, status):
-	if status[lang] == 0:
-		node.status.raw = '<a href="%s"><img src="/img/no.png" alt="no" title="%s - <?=_("translation not available")?>" /></a>' % (trans_url % (lang, package), lang)
-	else:
-		node.status.raw = '<a href="%s"><img src="/img/ok.png" alt="<?=_("yes")?>" title="%s - <?=_("translated")?>" /></a><a href="%s"><img src="/img/go.png" alt="<?=_("edit")?>" title="%s - <?=_("edit translation")?>" /></a>' % ("/ddtp/%s.php" % package, lang, trans_url % (lang, package), lang)
-
-#tem:
-#	con:package
-#	rep:langs
-#		con:name
-#		con:short
-#		con:long                                                               
-def renderStatic(node, package):
-	node.package.content = package
-	node.langs.repeat(renderSingleTrans, dict[package], package)
-	
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-
-def renderSingleTrans(node, lang, package):
-	node.name.raw = '<img src="/img/langs/%s.png" alt="%s" title="%s"/>' % (lang, lang, lang)
-	if dict[package][lang]:
-		node.short.raw = shorts[package][lang]
-		node.long.raw = longs[package][lang]
-	else:
-		node.short.raw = '<?=_("untranslated")?>'
-		node.long.raw = '<?=_("untranslated")?><br /><?=_("Please follow the link below to start translating")?>:<br /><br /><a href="%s">%s</a>' % (trans_url % (lang, package), trans_url % (lang, package))
-	
-for lang in langs:
-	url = ddtp_url % lang
-	pos = url.rfind("/")
-	name = "%s/data/ddtp/%s" % (base, url[pos + 1:])
-	urlretrieve(url, name)
-	
-packages = parseTasks()
-#packages = ["adun.app", "gcc", "emboss-explorer"]
-packages.sort()
-
-# XXX TODO HACK NEWS
-# Re-enable this to force-fetch the packages.
-# Shouldn't be done unless having talked to DDTSS admins.
-#  -- David
-#force_fetch(packages)
-for item in packages:
-	get_status(item)
-	parse_description(item)
-
-#print dict
-#print longs
-#print shorts
-
-# Let's generate the overview page
-f = open("%s/htdocs/ddtp.tmpl" % base)
-tmpl = HTMLTemplate.Template(renderTemplate, f.read())
-f.close()
-
-f = open("%s/static/ddtp.php" % base, "w")
-f.write(tmpl.render(langs, dict))
-f.close()
-
-# Let's generate nice static overview per-package pages
-for name in packages:
-	outfile = "%s/static/ddtp/%s.php" % (base, name)
-	f = open("%s/htdocs/ddtp_details.tmpl" % base)
-	tmpl = HTMLTemplate.Template(renderStatic, f.read())
-	f.close()
-	
-#	print tmpl.structure()
-	
-	f = open(outfile, "w")
-	f.write(tmpl.render(name))
-	f.close()

Deleted: trunk/community/infrastructure/update-tasks
===================================================================
--- trunk/community/infrastructure/update-tasks	2008-02-02 11:03:47 UTC (rev 1257)
+++ trunk/community/infrastructure/update-tasks	2008-02-02 12:12:23 UTC (rev 1258)
@@ -1,319 +0,0 @@
-#!/usr/bin/python -W ignore
-
-#
-# This Python script is:
-#  (C) 2007, David Paleino <d.paleino at gmail.com>
-#
-# It is licensed under the terms of GNU General Public License (GPL)
-# v3, or any later revision.
-#
-
-import apt
-import apt_pkg
-import apt_inst
-import HTMLTemplate
-import re
-import sys
-import time
-from datetime import datetime
-from email.Utils import formatdate
-from Tools import *
-
-base = "/var/lib/gforge/chroot/home/groups/debian-med"
-tasks = "%s/scripts/tasks" % base
-
-official = {}	# Official packages
-todo = {}		# Packages not in repositories, nor unofficial,
-				# nor prospected. They will eventually go into
-				# "unavailable".
-det = {}		# Official Packages details
-
-# let's get our nice dict in the form:
-# { 'task_foo': ['package1', 'package2', '...'],
-#   'task_bar': ['package3', 'package4', '...']}
-
-packages = parseTasks(None, True)
-unofficial = parseTasksNonOff()
-unavailable = parseTasksUnavail()
-task_details = parseTaskDetails()
-
-tasks = packages.keys()
-tasks.sort()
-
-apt_pkg.init()
-#~ apt_pkg.Config.Set("APT::Acquire::Translation", "it")
-
-cache = apt_pkg.GetCache()
-depcache = apt_pkg.GetDepCache(cache)
-aptcache = apt.Cache()
-
-###
-# Wrappers around apt_pkg
-###
-
-def __getRecords(package):
-	### TODO: convert to Python API
-	(f, index) = depcache.GetCandidateVer(cache[package]).TranslatedDescription.FileList.pop(0)
-	records = apt_pkg.GetPkgRecords(cache)
-	records.Lookup ((f, index))
-	return records
-
-def __getDesc(package):
-	regex = r"""(?P<short>.*)
-(?P<long>(^ .*$\n)+\n?)"""
-
-	p = re.compile(regex, re.MULTILINE)
-	m = p.match(getSections(package)['Description'])
-	if m:
-		return {'ShortDesc': m.group("short"), 'LongDesc': m.group("long")}
-	else:
-		return False
-
-def getShort(package):
-	### TODO: convert to Python API
-	desc = __getDesc(package)
-	if desc:
-		return desc['ShortDesc']
-	else:
-		# Fallback to the C++ wrapper
-		return __getRecords(package).ShortDesc
-
-def getLong(package):
-	### TODO: convert to Python API
-	desc = __getDesc(package)
-	if desc:
-		return desc['LongDesc']
-	else:
-		# Fallback to the C++ wrapper
-		return __getRecords(package).LongDesc
-
-def getHomepage(package):
-	sect = getSections(package)
-	try:
-		return sect['Homepage']
-	# Fallback to the old "  Homepage: ..." pseudo-field
-	# TODO: also renders _wrong_ "URL" pseudo-fields! Fix the packages!
-	except:
-		p = re.compile(".*(?P<field>Homepage|URL): (?P<url>.*)", re.DOTALL)
-		m = p.match(getLong(package))
-		if m:
-			tmp = det[package]['LongDesc']
-			det[package]['LongDesc'] = tmp.replace(m.group("field") + ": " + m.group("url"), "")
-			return m.group("url")
-		else:
-			# We don't have any valid field for homepage,
-			# return a suitable "#" for <a href>s.
-			return "#"
-
-def getDebUrl(package):
-	try:
-		return getSections(package)["Filename"]
-	except:
-		return "#"
-
-def getVersion(package):
-	try:
-		return getSections(package)["Version"]
-	except:
-		# Fallback to the C++ wrapper
-		for pkg in cache.Packages:
-			if pkg.Name in det:
-				if not pkg.VersionList:
-					return "N/A"
-				else:
-					return pkg.VersionList[0].VerStr
-
-def getLicense(package):
-	### FIX
-	return "GPL-foo"
-
-def getSections(package):
-	pkg = aptcache[package]
-	pkg._lookupRecord(True)
-	return apt_pkg.ParseSection(pkg._records.Record)
-
-###
-# Template handlers
-###
-
-def renderIndex(node, tasks):
-	node.tasks.repeat(renderTaskList, tasks)
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-
-def renderTaskList(node, task):
-	node.task.raw = """<a href="/tasks/%s.php" name="%s" id="%s">%s</a>""" % (task, task, task, task.capitalize())
-
-def renderTasks(node, task, packages, details):
-	node.task.content = details['Task']
-	node.shortdesc.content = details['ShortDesc']
-	node.heading.content = details['ShortDesc']
-	node.longdesc.content = details['LongDesc']
-
-	t = datetime.now()
-	node.date.content = formatdate(time.mktime(t.timetuple()))
-
-	# Let's separate official packages from others
-	for pkg in packages:
-		# If the package has a short description in cache,
-		# there's an high chance it is an official package.
-		# Probably we can use a better algorithm? (I believe
-		# Alioth's APT cache won't be contaminated by external
-		# repositories)
-		try:
-			short = getShort(pkg)
-			if not task in official:
-				official[task] = []
-			official[task].append(pkg)
-			det[pkg] = {}
-			det[pkg]['ShortDesc'] = short
-			det[pkg]['LongDesc'] = getLong(pkg).replace("%s\n" % short, "")
-			det[pkg]['LongDesc'] = det[pkg]['LongDesc'].replace("<", "&lt;").replace(">", "&gt;")
-			# getHomepage() does some magic on ['LongDesc']
-			det[pkg]['Homepage'] = getHomepage(pkg)
-			det[pkg]['LongDesc'] = det[pkg]['LongDesc'].replace("\n .\n", "<br /><br />").replace("\n", "")
-			det[pkg]['Version'] = getVersion(pkg)
-			det[pkg]['License'] = getLicense(pkg)
-			det[pkg]['Task'] = task
-			det[pkg]['Pkg-URL'] = "http://packages.debian.org/unstable/%s/%s" % (getSections(pkg)['Section'], pkg)
-			### BUG: some packages don't get the right Filename
-			###      see, for example, "treeviewx": it has a Filename
-			###      field, but doesn't get parsed.
-			### FIX: installed packages with versions newer than the
-			###      one in repositories don't have that field. :@!
-			det[pkg]['Deb-URL'] = "http://ftp.debian.org/%s" % getDebUrl(pkg)
-		except:
-			pass
-
-	if task in official:
-		node.official_head.raw = """<h2>
-<a id="official-debs" name="official-debs"></a>
-	Official Debian packages
-</h2>"""
-		node.official.repeat(renderOfficial, official[task], task)
-
-	if task in todo:
-		error = True
-	else:
-		error = False
-
-	if task in unofficial:
-		node.unofficial_head.raw = """<h2>
-<a id="inofficial-debs" name="inofficial-debs"></a>
-	Inofficial Debian packages
-</h2>"""
-		node.unofficial.repeat(renderUnofficial, unofficial[task])
-		error = False
-
-	if task in unavailable:
-		node.unavailable_head.raw = """<h2>
-<a id="debs-not-available" name="debs-not-available"></a>
-	Debian packages not available
-</h2>"""
-		node.unavailable.repeat(renderUnavailable, unavailable[task])
-		error = False
-
-	if error:
-		# The package probably needs a proper prospective entry in the
-		# task files. Write it to stdout.
-		print "Error: problems with %s" % task
-
-def renderOfficial(node, package, task):
-	# Here we parse just official packages
-	node.shortdesc.content = det[package]['ShortDesc']
-	node.project.raw = "<table class=\"project\" summary=\"%s\">" % package
-	node.anchor.atts['name'] = package
-	node.anchor.atts['id'] = package
-	node.name.content = package.capitalize()
-	node.url.atts['href'] = det[package]['Homepage']
-	if det[package]['Homepage'] == "#":
-		node.url.content = "Homepage not available"
-	else:
-		node.url.content = det[package]['Homepage']
-
-	node.longdesc.raw = det[package]['LongDesc']
-	node.version.content = "Version: %s" % det[package]['Version']
-	node.license.content = "License: %s" % det[package]['License']
-	node.pkgurl.atts['href'] = det[package]['Pkg-URL']
-	node.pkgurl.content = "Official Debian package"
-	node.deburl.atts['href'] = det[package]['Deb-URL']
-	#~ node.deburl.content = "X" ### TODO: add a nice icon here to download the .deb package
-	node.deburl.raw = "<img src=\"/img/deb-icon.png\" />"
-
-def renderUnofficial(node, package):
-	# Here we parse just unofficial packages
-	node.shortdesc.content = package['ShortDesc']
-	node.longdesc.raw = package['LongDesc']
-	node.project.raw = "<table class=\"project\" summary=\"%s\">" % package['Package']
-	node.anchor.atts['name'] = package['Package']
-	node.anchor.atts['id'] = package['Package']
-	node.name.content = package['Package'].capitalize()
-	node.url.atts['href'] = package['Homepage']
-	node.url.content = package['Homepage']
-	node.license.content = "License: %s" %package['License']
-	node.pkgurl.atts['href'] = package['Pkg-URL']
-	node.pkgurl.content = "Inofficial Debian package"
-
-	# Let's try to get the version from the package name
-	# (following Debian standards: <name>_<ver>_<arch>.deb)
-	regex = ".*/%s_(?P<version>.*)_.*\.deb$" % package['Package']
-	p = re.compile(regex)
-	m = p.search(package['Pkg-URL'])
-	if m:
-		node.version.content = "Version: %s" % m.group("version")
-	else:
-		node.version.content = "Version: N/A"
-
-
-def renderUnavailable(node, package):
-	# Parsing unavailable packages :(
-	# PACKAGE THEM! :)
-	name = package['Package']
-	if package['ShortDesc']:
-		node.shortdesc.content = package['ShortDesc']
-	else:
-		node.shortdesc.content = "N/A"
-	node.longdesc.raw = package['LongDesc']
-	node.project.raw = "<table class=\"project\" summary=\"%s\">" % name
-	if package['Responsible']:
-		node.responsible.content = package['Responsible']
-	else:
-		node.responsible.raw = "no one"
-	if package['WNPP']:
-		node.wnpp.raw = " &mdash; <a href=\"http://bugs.debian.org/%s\">wnpp</a>" % package['WNPP']
-	node.anchor.atts['name'] = name
-	node.anchor.atts['id'] = name
-	node.name.content = name.capitalize()
-	if package['Homepage']:
-		node.url.atts['href'] = package['Homepage']
-		node.url.content = package['Homepage']
-	else:
-		node.url.atts['href'] = "#"
-		node.url.content = "N/A"
-	if package['License']:
-		node.license.raw = "<?=_('License')?>: %s" % package['License']
-	else:
-		node.license.raw = "<?=_('License')?>: N/A"
-
-# Let's render the Tasks Page index, first
-f = open("%s/htdocs/tasks_idx.tmpl" % base)
-tmpl = HTMLTemplate.Template(renderIndex, f.read())
-f.close()
-f = open("%s/static/tasks/index.php" % base, "w")
-f.write(tmpl.render(tasks))
-f.close()
-
-# Let's render single pages now.
-f = open("%s/htdocs/tasks.tmpl" % base)
-tmpl = HTMLTemplate.Template(renderTasks, f.read())
-f.close()
-
-for task in tasks:
-	f = open("%s/static/tasks/%s.php" % (base, task), "w")
-
-	# This is to avoid useless <br>eaks before closing the cell
-	source = tmpl.render(task, packages[task], task_details[task])
-	f.write(re.sub(r"<br /><br />[ ]*</td>", "</td>", source))
-
-	f.close()
-

Deleted: trunk/community/infrastructure/update-tasks-wrapper
===================================================================
--- trunk/community/infrastructure/update-tasks-wrapper	2008-02-02 11:03:47 UTC (rev 1257)
+++ trunk/community/infrastructure/update-tasks-wrapper	2008-02-02 12:12:23 UTC (rev 1258)
@@ -1,4 +0,0 @@
-#!/bin/bash
-SCRIPT=/var/lib/gforge/chroot/home/groups/debian-med/scripts/update-tasks
-
-python $SCRIPT 2&>/dev/null




More information about the debian-med-commit mailing list