Index: contrib/hook-scripts/svn2atom.py
===================================================================
--- contrib/hook-scripts/svn2atom.py	(revision 0)
+++ contrib/hook-scripts/svn2atom.py	(revision 0)
@@ -0,0 +1,297 @@
+#!/usr/bin/env python
+
+"""Usage: svn2atom.py [OPTION...] REPOS-PATH
+
+Generate an Atom 1.0 feed file containing commit information for the
+Subversion repository located at REPOS-PATH.  The item title is the
+revision number, and the item description contains the author, date,
+log messages and changed paths.
+
+Options:
+
+ -h, --help             Show this help message.
+ 
+ -f, --atom-file=PATH   Store the Atom feed in the file located at PATH,
+                        which will be created if it doesn't already
+                        exist and overwritten if it already exists.  If 
+                        not provided, the script will store the feed in
+                        the current working directory, in a file named
+                        REPOS_NAME.atom (where REPOS_NAME is the basename
+                        of the REPOS_PATH command-line argument).
+ 
+ -r, --revision=X[:Y]   Subversion revision (or revision range) to generate
+                        Atom info for.  If not provided, info for the single
+                        youngest revision in the repository will be generated.
+ 
+ -u, --item-url=URL     Use URL as the basis for generating item links.  This
+                        value is used as a url template so the string 
+                        '?rev=rev_no' is appended to form the associated item 
+                        link.
+ 
+ -U, --feed-url=URL     Use URL as the global Atom feed link. This value
+                        is used in the Atom feed header section to point
+                        the location of the originating web site.
+
+ -P, --svn-path=DIR     Look in DIR for the svnlook binary.  If not provided,
+                        "svnlook" must be on the PATH.
+"""
+
+import sys
+
+# Python 2.3 is required by PyRSS2Gen
+py_version  = sys.version_info
+if sys.version_info[0:2] < (2,3):
+    sys.stderr.write("Error: Python 2.3 or higher required.\n")
+    sys.exit(1)
+
+# Import some standard stuff.
+import getopt
+import os
+import popen2
+import datetime
+from xml.dom import getDOMImplementation
+
+def usage_and_exit(errmsg=None):
+    """Print a usage message, plus an ERRMSG (if provided), then exit.
+    If ERRMSG is provided, the usage message is printed to stderr and
+    the script exits with a non-zero error code.  Otherwise, the usage
+    message goes to stdout, and the script exits with a zero
+    errorcode."""
+    if (errmsg is not None):
+      stream = sys.stderr
+    else:
+      stream = sys.stdout
+
+    print >> stream, __doc__
+    if errmsg:
+        print >> stream, "\nError: %s" % (errmsg)
+        sys.exit(2)
+    sys.exit(0)
+
+def check_url(url, opt):
+    """Verify that URL looks like a valid URL or option OPT."""
+    if not (url.startswith('https://') \
+            or url.startswith('http://') \
+            or url.startswith('file://')):
+      usage_and_exit("svn2atom.py: Invalid url '%s' is specified for " \
+                     "'%s' option" % (url, opt))
+
+class Svn2Atom:
+    def __init__(self, svn_path, repos_path, item_url, atom_file, feed_url):
+        self.repos_path = repos_path
+        self.item_url = item_url or ""
+        self.atom_file = atom_file
+        self.feed_url = feed_url or ""
+        self.svnlook_cmd = 'svnlook'
+        if svn_path is not None:
+            self.svnlook_cmd = os.path.join(svn_path, 'svnlook')
+
+        self.init_output()
+        self.add_header()
+        
+    def init_output(self):
+        """ initialize atom output """
+        self.document = getDOMImplementation().createDocument(None,None,None)
+        self.feed = self.document.createElement("feed")
+        self.document.appendChild(self.feed)
+
+    def add_header(self):
+        """ add atom xml header """
+
+        doc = self.document
+        self.feed.setAttribute("xml:lang", "en")
+        self.feed.setAttribute("xmlns", "http://www.w3.org/2005/Atom")
+
+        self.title = self.document.createElement("title")
+        self.feed.appendChild(self.title)
+        self.title.appendChild(doc.createTextNode("%s Subversion Commits Feed" % 
+        (os.path.basename(self.repos_path))))
+
+        self.id = self.document.createElement("id")
+        self.feed.appendChild(self.id)
+        self.id.appendChild(doc.createTextNode("%s" % self.feed_url))
+
+        self.updated = self.document.createElement("updated")
+        self.feed.appendChild(self.updated)
+        self.updated.appendChild(\
+        doc.createTextNode("%s" % self.format_date(datetime.datetime.now())))
+
+        self.link = self.document.createElement("link")
+        self.feed.appendChild(self.link)
+        self.link.setAttribute("href", self.feed_url)
+
+        self.author = self.document.createElement("author")
+        self.feed.appendChild(self.author)
+        self.aname = self.document.createElement("name")
+        self.author.appendChild(self.aname)
+        self.aname.appendChild(doc.createTextNode("subversion"))
+
+    def add_entry(self, revision):
+        """ add new atom entry for a revision """
+        self.revision = str(revision)
+        url = self.item_url and \
+              self.item_url + "?rev=%s" % self.revision
+
+        doc = self.document
+        self.entry = self.document.createElement("entry")
+        self.feed.appendChild(self.entry)
+        self.entry.appendChild(doc.createTextNode("\n  "))
+
+        self.id = self.document.createElement("id")
+        self.entry.appendChild(self.id)
+        self.id.appendChild(doc.createTextNode("%s" % url))
+
+        self.title = self.document.createElement("title")
+        self.entry.appendChild(self.title)
+        self.title.appendChild(doc.createTextNode(\
+          "Revision %s" % self.revision))
+
+        self.updated = self.document.createElement("updated")
+        self.entry.appendChild(self.updated)
+        self.updated.appendChild(\
+        doc.createTextNode("%s" % self.format_date(datetime.datetime.now())))
+
+        self.link = self.document.createElement("link")
+        self.entry.appendChild(self.link)
+        self.link.setAttribute("href", url)
+
+        self.summary = self.document.createElement("summary")
+        self.entry.appendChild(self.summary)
+
+        description = self.make_atom_item_desc()
+        self.summary.appendChild(doc.createTextNode(description))
+
+    def finish_output(self):
+        """ write the atom feed to the file """
+        t = self.document.createTextNode("\n")
+        self.feed.appendChild(t)
+        fp = open(self.atom_file, "w")
+        fp.write(self.document.toxml())
+        fp.write("\n")
+        fp.close()
+
+    def make_atom_item_desc(self):
+        cmd = [self.svnlook_cmd, 'info', '-r', self.revision, self.repos_path]
+        std_out, std_in, std_err = popen2.popen3(cmd)
+        cmd_out = std_out.readlines()
+        cmd_err = std_err.readlines()
+
+        author = "Author: " + cmd_out[0]
+        commit_date = "Date: " + cmd_out[1]
+        new_revision = "Revision: %s\n" % self.revision
+        commit_log = "Log: " + cmd_out[3]
+        std_out.close()
+        std_in.close()
+        std_err.close()
+
+        cmd = [self.svnlook_cmd, 'changed', '-r', self.revision, 
+               self.repos_path]
+        std_out, std_in, std_err = popen2.popen3(cmd)
+        cmd_out = std_out.readlines()
+        changed_files = "Modified: "
+        for item in cmd_out:
+            changed_files += item
+
+        desc_lines = []
+        desc_lines.append(author)
+        desc_lines.append(commit_date)
+        desc_lines.append(self.revision)
+        desc_lines.append(commit_log)
+        desc_lines.append(changed_files)
+
+        item_desc = "\n".join(desc_lines)
+        std_out.close()
+        std_in.close()
+        std_err.close()
+
+        return item_desc
+
+    def format_date(self, dt):
+        """ input date must be in GMT """
+        return "%04d-%02d-%02dT%02d:%02d:%02d.%02dZ" % \
+                (dt.year, dt.month, dt.day, dt.hour, dt.minute,
+                dt.second, dt.microsecond)
+
+def main():
+    # Parse the command-line options and arguments.
+    try:
+        opts, args = getopt.gnu_getopt(sys.argv[1:], "hP:r:u:f:U:",
+                                       ["help",
+                                        "svn-path=",
+                                        "revision=",
+                                        "item-url=",
+                                        "atom-file=",
+                                        "feed-url=",
+                                        ])
+    except getopt.GetoptError, msg:
+        usage_and_exit(msg)
+
+    # Make sure required arguments are present.
+    if len(args) != 1:
+        usage_and_exit("You must specify a repository path.")
+    repos_path = args[0]
+
+    # Now deal with the options.
+    commit_rev = None
+    svn_path = item_url = feed_url = None
+    atom_file = os.path.basename(repos_path) + ".atom"
+    
+    for opt, arg in opts:
+        if opt in ("-h", "--help"):
+            usage_and_exit()
+        elif opt in ("-P", "--svn-path"):
+            svn_path = arg
+        elif opt in ("-r", "--revision"):
+            commit_rev = arg
+        elif opt in ("-u", "--item-url"):
+            item_url = arg
+            check_url(item_url, opt)
+        elif opt in ("-f", "--atom-file"):
+            atom_file = arg
+        elif opt in ("-U", "--feed-url"):
+            feed_url = arg
+            check_url(feed_url, opt)
+    
+    if commit_rev is None:
+        svnlook_cmd = 'svnlook'
+        if svn_path is not None:
+            svnlook_cmd = os.path.join(svn_path, 'svnlook')
+        std_out, std_in, std_err = popen2.popen3([svnlook_cmd, 'youngest', 
+                                   repos_path])
+        cmd_out = std_out.readlines()
+        revisions = [int(cmd_out[0])]
+        std_out.close()
+        std_in.close()
+        std_err.close()
+    else:
+        try:
+            rev_range = commit_rev.split(':')
+            len_rev_range = len(rev_range)
+            if len_rev_range == 1:
+                revisions = [int(commit_rev)]
+            elif len_rev_range == 2:
+                start, end = rev_range
+                start = int(start)
+                end = int(end)
+                if (start > end):
+                    tmp = start
+                    start = end
+                    end = tmp
+                revisions = []
+                while (start <= end):
+                    revisions.append(start)
+                    start = start + 1
+            else:
+                usage_and_exit("svn2atom.py: Invalid value '%s' for --revision." \
+                               % (commit_rev))
+        except ValueError, msg:
+            usage_and_exit("svn2atom.py: Invalid value '%s' for --revision." \
+                           % (commit_rev))
+    
+    svn2atom = Svn2Atom(svn_path, repos_path, item_url, atom_file, feed_url)
+    for revision in revisions:
+      svn2atom.add_entry(revision)
+    svn2atom.finish_output()
+    
+if __name__ == "__main__":
+    main()

Property changes on: contrib/hook-scripts/svn2atom.py
___________________________________________________________________
Name: svn:executable
   + *

Index: www/tools_contrib.html
===================================================================
--- www/tools_contrib.html	(revision 20738)
+++ www/tools_contrib.html	(working copy)
@@ -116,6 +116,7 @@
     <li><a href="#svn_log_pl">svn-log.pl</a></li>
     <li><a href="#svn2cl_sh">svn2cl.sh</a></li>
     <li><a href="#svn2rss_py">svn2rss.py</a></li>
+    <li><a href="#svn2atom_py">svn2atom.py</a></li>
   </ul></li>
   <li><strong>merge</strong><ul>
     <li><a href="#svnmerge_py">svnmerge.py</a></li>
@@ -904,6 +905,10 @@
     (<a href="http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts">contrib/hook-scripts</a>)</h3>
   <p>Generates a RSS 2.0 file containing commit information.</p>
 
+  <h3><a name="svn2atom_py"
+    href="http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/svn2atom.py">svn2rss.py</a>
+    (<a href="http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts">contrib/hook-scripts</a>)</h3>
+  <p>Generates a ATOM 1.0 file containing commit information.</p>
 
   <h3><a name="svnperms_py"
     href="http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/svnperms.py">svnperms.py</a>
