[Treepkg-commits] r2 - in trunk: . bin enterprise test treepkg

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Thu May 3 20:31:32 CEST 2007


Author: bh
Date: 2007-05-03 20:31:32 +0200 (Thu, 03 May 2007)
New Revision: 2

Added:
   trunk/COPYING
   trunk/README
   trunk/bin/
   trunk/bin/createstaticweb.py
   trunk/bin/initpbuilder.py
   trunk/bin/publishstaticweb.py
   trunk/bin/reportstatus.py
   trunk/bin/runtreepkg.py
   trunk/bin/starttreepkgweb.py
   trunk/bin/treepkgcmd.py
   trunk/cherrypy.cfg
   trunk/demo.cfg
   trunk/demostaticweb.cfg
   trunk/enterprise/
   trunk/enterprise/__init__.py
   trunk/enterprise/kdei18n.py
   trunk/enterprise/kdepim.py
   trunk/test/
   trunk/test/runtests.py
   trunk/test/test_cmdexpand.py
   trunk/test/test_status.py
   trunk/treepkg/
   trunk/treepkg/__init__.py
   trunk/treepkg/cmdexpand.py
   trunk/treepkg/options.py
   trunk/treepkg/packager.py
   trunk/treepkg/readconfig.py
   trunk/treepkg/report.py
   trunk/treepkg/run.py
   trunk/treepkg/status.py
   trunk/treepkg/subversion.py
   trunk/treepkg/util.py
   trunk/treepkg/web-status.html
   trunk/treepkg/web.py
Log:
initial import

Added: trunk/COPYING
===================================================================
--- trunk/COPYING	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/COPYING	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.


Property changes on: trunk/COPYING
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/README
===================================================================
--- trunk/README	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/README	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,166 @@
+README for TreePackager
+=======================
+
+
+TreePackager is a tool to automatically build debian packages from SVN.
+
+
+Prerequisites
+-------------
+
+You need the following software to run TreePackager.  In the list below,
+parentheses contain the name of the corresponding package in Debian Etch
+if its not the same as the software.  The version required is usually
+the one from debian etch.
+
+  Python 2.4   (python2.4-minimal)
+  Debian devscripts (devscripts)
+  subversion
+  pbuilder
+  sudo
+  bzip2
+
+For the web front-end you also need the following software:
+
+  Genshi (python-genshi)
+  CherryPy (python-cherrypy)
+
+Some of the packagers require additional software.  The KDEPIM
+enterprise branch packagers require the following additional software:
+
+  autoconf2.13
+  automake1.9
+
+
+Installation
+------------
+
+You can run the tree packager itself directly from the source tree.
+However, you need to configure it first and setup pbuilder.
+
+
+Configuration
+-------------
+
+To understand the configuration, first a few notes about the
+architecture of TreePackager.  The TreePackager consist of one program
+that periodically updates svn working directories and if something has
+changed, builds a new debian package from the working directory.  The
+program should run as a normal user.  The sample configuration assumes
+that it's the user "builder" with a home directory "/home/builder".  The
+default configuration manages a directory tree under
+"/home/builder/enterprise".
+
+The binary packages are built with pbuilder.  Because pbuilder uses a
+chroot environment to build the packages, it must be run as root.  The
+tree packager therefore needs a way to run pbuilder as root even though
+itself runs as a non-root user.  By default the tree packager uses sudo,
+so you have to setup sudo to allow the tree packager user to invoke
+pbuilder without a password.  This can be accomplished with the
+following line in /etc/sudoers (using the default user name):
+
+   builder ALL = NOPASSWD: /usr/sbin/pbuilder
+
+
+Configure TreePackager
+~~~~~~~~~~~~~~~~~~~~~~
+
+The file demo.cfg contains example configuration that contains most of
+what is needed to package KDEPIM and kde-i18n from the KDEPIM enterprise
+branch.  Copy this file to treepkg.cfg and adapt it to your needs.  The
+comments in the file should provide most of the hints to get you
+started.  Some more information is in the "Configuring a packager"
+section below.
+
+
+Configuring a packager
+~~~~~~~~~~~~~~~~~~~~~~
+
+The configuration file contains one section for each packager.  The
+section name starts with a "pkg_" prefix.  The possible options are
+described in demo.cfg.  However there are some things that need to be
+set up outside of the config file.
+
+Each packager has a base directory (the base_dir option in the
+corresponding pkg_-section).  One thing needed by a packager is the
+contents of the debian sub-directory of the debian source package.  When
+creating the source package, the packager simply copies the
+sub-directory "debian" of the base_dir into the directory making up the
+source tree.  How the debian directory is created and maintained is up
+to you.  Usually it's a good idea to start with the debian sub-directory
+an existing debian package for the software.
+
+
+Configure pbuilder
+~~~~~~~~~~~~~~~~~~
+
+It's best to give the tree packager its own pbuilder configuration and
+directories.  The default configuration uses a "pbuilder" sub-directory
+in /home/builder/enterprise.  If you have created the treepkg.cfg file
+with at least one packager and the correct root_cmd and pbuilderrc
+options (the defaults for both should be OK if you use sudo as described
+above), you can create the directories, the pbuilder configuration and
+the chroot environment with the script initpbuilder.py like this:
+
+   bin/initpbuilder.py --mirrorsite=<URL of preferred debian mirror>
+
+You can specify some more mirrors with the --othermirror option.  For
+more information run "bin/initpbuilder.py --help" and consult the pbuilder
+documentation.
+
+
+Configure the web front-end
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The web front-end consists of a single HTML-page with an overview of the
+packager status and links to build logs when available.  There are two
+ways to publish this front-end: as little web-server with a dynamic
+web-page or as a directory with a bunch of files making up a static
+web-site.
+
+Web-server:
+
+The default configuration should be OK in most cases.  If you want you
+can customize it in cherrypy.cfg.  Start the web front-end with
+
+  bin/starttreepkgweb.py
+
+starttreepkgweb has some options to specify which configuration files to use.
+
+
+Static pages:
+
+The static pages are published using two programs, createstaticweb.py
+and publishstaticweb.py.  createstaticweb.py is run on the system where
+the tree packager runs.  publishstaticweb.py is usually run on another
+system and connects via ssh and rsync to the tree packager host, creates
+the files with createstaticweb.py and copies the files from the tree
+packager host to a third host.
+
+The configuration file for publishstaticweb.py is demostaticweb.cfg.
+Copy this file to staticweb.cfg and adapt it to your system.  The
+comments in the file describe the options.  Afterwards, run the script
+with
+
+  bin/publishstaticweb.py
+
+
+Running the Tree Packager
+-------------------------
+
+After configuration, run the tree packager with
+
+  bin/runtreepkg.py  [options] [packager...]
+
+For each packager listed on the command line -- or all configured
+packagers if none are given -- the tree packager checks out or updates
+the sources and builds binary packages if the new revision hasn't been
+packaged yet.
+
+If the option --once has been given, the tree packager exits after it
+has checked each packager once.  Without it, the check is done
+repeatedly.  The interval between two checks can be set in the configuration
+file.
+
+Call runtreepkg.py with the --help option to see a list of the available
+options.


Property changes on: trunk/README
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/bin/createstaticweb.py
===================================================================
--- trunk/bin/createstaticweb.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/createstaticweb.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,29 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Creates a static web-site with a status report"""
+
+import sys
+import os
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.web import Status
+
+def parse_commandline():
+    return create_parser().parse_args()
+
+def create_static_site(treepkg_config, destdir):
+    status = Status(treepkg_config=treepkg_config)
+    status.create_static_site(destdir)
+
+def main():
+    options, args = parse_commandline()
+    create_static_site(options.config_file, args[0])
+
+main()


Property changes on: trunk/bin/createstaticweb.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/initpbuilder.py
===================================================================
--- trunk/bin/initpbuilder.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/initpbuilder.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,125 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Script to initialize the pbuilder environment for the tree packager
+
+The script assumes that the config file for the tree packager already
+contains the pbuilder settings.  Also, this script assumes that there is
+only one pbuilder setting for all packagers.
+"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.packager import create_package_track, PackagerGroup
+from treepkg.readconfig import read_config
+from treepkg.util import ensure_directory, writefile
+from treepkg.run import call
+
+
+pbuilderrc_template = '''\
+# This file was automatically generated by initpbuilder.py.
+# for the possible settings see "man pbuilderrc"
+
+BASETGZ=%(basedir)s/base.tgz
+BUILDPLACE=%(builddir)s
+USEPROC=yes
+USEDEVPTS=yes
+BUILDRESULT=%(resultdir)s
+DISTRIBUTION=%(distribution)s
+APTCACHE=%(basedir)s/aptcache
+APTCACHEHARDLINK=yes
+REMOVEPACKAGES=lilo
+MIRRORSITE="%(mirrorsite)s"
+OTHERMIRROR="%(othermirror)s"
+BINDMOUNTS="%(extra-pkgdir)s"
+PKGNAME_LOGFILE=yes
+'''
+
+
+def init_pbuilder(pbuilderrc, distribution, mirrorsite, extramirrors, root_cmd):
+    if not os.path.isabs(pbuilderrc):
+        print >>sys.stderr, "pbuilderrc must be an absolute filename"
+        sys.exit(1)
+
+    if os.path.exists(pbuilderrc):
+        print >>sys.stderr, "pbuilderrc %r already exists." % pbuilderrc
+        sys.exit(1)
+
+    basedir = os.path.dirname(pbuilderrc)
+    replacements = dict(basedir=basedir,
+                        distribution=distribution,
+                        mirrorsite=mirrorsite)
+
+    # create the pbuilder directories.  basedir is created implicitly by
+    # creating its subdirectories.
+    for subdir in ["base", "build", "result", "aptcache", "extra-pkg"]:
+        directory = os.path.join(basedir, subdir)
+        replacements[subdir + "dir"] = directory
+        print "creating directory:", repr(directory)
+        ensure_directory(directory)
+
+    # build OTHERMIRROR value.  We always include the extra-pkg dir.
+    othermirror = "deb file://%(extra-pkgdir)s ./" % replacements
+    if extramirrors:
+        othermirror += " | " + extramirrors
+    replacements["othermirror"] = othermirror
+
+    # create the pbuilderrcfile
+    print "creating pbuilderrc:", repr(pbuilderrc)
+    writefile(pbuilderrc, pbuilderrc_template % replacements)
+
+    # turn the extra-pkg directory into a property deb archive
+    print "turning the extra-pkg dir into a debian archive"
+    extra_pkgdir = replacements["extra-pkgdir"]
+    call(["apt-ftparchive", "packages", "."],
+         stdout=open(os.path.join(extra_pkgdir, "Packages"), "w"),
+         cwd=extra_pkgdir)
+
+    # create the base.tgz chroot
+    print "running pbuilder create"
+    call(root_cmd + ["pbuilder", "create", "--configfile", pbuilderrc])
+
+
+def parse_commandline():
+    parser = create_parser()
+    parser.set_defaults(distribution="etch")
+    parser.add_option("--mirrorsite",
+                      help=("The debian mirror site"
+                            " (pbuilder MIRRORSITE setting).  Required."))
+    parser.add_option("--othermirror",
+                      help=("Extra contents of the OTHERMIRROR setting."
+                            " See the pbuilder documentation for the format."))
+    parser.add_option("--distribution",
+                      help=("The debian distribution for the pbuilder chroot."
+                            " Default is etch."))
+    return parser.parse_args()
+
+
+def main():
+    options, args = parse_commandline()
+
+    if options.mirrorsite is None:
+        print >>sys.stderr, "Missing required option --mirrorsite"
+        sys.exit(1)
+
+    treepkg_opts, packager_opts = read_config(options.config_file)
+    group = PackagerGroup([create_package_track(**opts)
+                           for opts in packager_opts],
+                          **treepkg_opts)
+    track = group.get_package_tracks()[0]
+    init_pbuilder(track.pbuilderrc,
+                  distribution=options.distribution,
+                  mirrorsite=options.mirrorsite,
+                  extramirrors=options.othermirror,
+                  root_cmd=track.root_cmd)
+
+main()


Property changes on: trunk/bin/initpbuilder.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/publishstaticweb.py
===================================================================
--- trunk/bin/publishstaticweb.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/publishstaticweb.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,75 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Publishes a static web-site with a status report"""
+
+import sys
+import os
+from optparse import OptionParser
+from ConfigParser import SafeConfigParser
+
+import treepkgcmd
+from treepkg.readconfig import read_config_section
+from treepkg.run import call
+from treepkg.cmdexpand import cmdexpand
+from treepkg.util import ensure_directory
+
+def remove_trailing_slashes(s):
+    return s.rstrip("/")
+
+def expand_filename(filename):
+    """
+    Applies os.path.expanduser and os.path.expandvars to filename
+    """
+    return os.path.expandvars(os.path.expanduser(filename))
+
+staticweb_desc = ["build_user", "build_host", "build_create",
+                  ("build_dir", remove_trailing_slashes),
+                  "publish_user", "publish_host",
+                  ("publish_dir", remove_trailing_slashes),
+                  ("cachedir",
+                   lambda s: expand_filename(remove_trailing_slashes(s)))]
+
+def read_config(filename):
+    parser = SafeConfigParser()
+    parser.read([filename])
+    return read_config_section(parser, "staticweb", staticweb_desc)
+
+def parse_commandline():
+    parser = OptionParser()
+    parser.set_defaults(config_file=os.path.join(treepkgcmd.topdir,
+                                                 "staticweb.cfg"))
+    parser.add_option("--config-file",
+                      help=("The tree packager config file."
+                            " Default staticweb.cfg"))
+    return parser.parse_args()
+
+def publish_static_site(config_filename):
+    config = read_config(config_filename)
+
+    # create web-page on build host
+    call(cmdexpand("ssh $build_user$@$build_host $build_create $build_dir",
+                   **config))
+
+    # rsync the new web-pages to the local cache
+    ensure_directory(config["cachedir"])
+    call(cmdexpand("rsync -rL --delete $build_user$@$build_host:$build_dir/"
+                   " $cachedir",
+                   **config))
+
+    # rsync the web pages from the local cache to the publishing host
+    call(cmdexpand("rsync -rL --delete $cachedir/"
+                   " $publish_user$@$publish_host:$publish_dir",
+                   **config))
+
+
+def main():
+    options, args = parse_commandline()
+    publish_static_site(options.config_file)
+
+main()


Property changes on: trunk/bin/publishstaticweb.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/reportstatus.py
===================================================================
--- trunk/bin/reportstatus.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/reportstatus.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,40 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Reports the current status of the tree packager"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.report import get_packager_group, prepare_report
+
+def parse_commandline():
+    return create_parser().parse_args()
+
+
+def report_text(group):
+    report = prepare_report(group)
+    for revno, row in report.revisions:
+        for col in row:
+            if col:
+                print "%s %s: %s" % (col.name, revno, col.status.desc)
+                if col.status.start:
+                    print "    Start:", col.status.start
+                    print "     Stop:", col.status.stop
+                print
+
+def main():
+    options, args = parse_commandline()
+    group = get_packager_group(options.config_file)
+    report_text(group)
+
+
+main()


Property changes on: trunk/bin/reportstatus.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/runtreepkg.py
===================================================================
--- trunk/bin/runtreepkg.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/runtreepkg.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,64 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Starts the tree packager"""
+
+import sys
+import os
+import logging
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.packager import create_package_track, PackagerGroup
+from treepkg.readconfig import read_config
+
+def initialize_logging():
+    """Initializes the logging system"""
+    root = logging.getLogger()
+    root.setLevel(logging.DEBUG)
+    hdlr = logging.StreamHandler()
+    fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
+    hdlr.setFormatter(fmt)
+    root.addHandler(hdlr)
+
+def parse_commandline():
+    parser = create_parser()
+    parser.add_option("--once", action="store_true",
+                      help=("Check the packagers only once and exit afterwards."
+                            " Without this option, the tree packager will"
+                            " check periodically."))
+    return parser.parse_args()
+
+def main():
+    options, args = parse_commandline()
+
+    initialize_logging()
+
+    treepkg_opts, packager_opts = read_config(options.config_file)
+
+    if args:
+        packager_opts = [opts for opts in packager_opts if opts["name"] in args]
+        # check whether we got all of the names in args:
+        for opts in packager_opts:
+            name = opts["name"]
+            if name in args:
+                args.remove(name)
+        for name in args:
+            print >>sys.stderr, "No package tracks found named %r" % name
+
+    if packager_opts:
+        group = PackagerGroup([create_package_track(**opts)
+                               for opts in packager_opts],
+                              **treepkg_opts)
+        if options.once:
+            group.check_package_tracks()
+        else:
+            group.run()
+
+main()


Property changes on: trunk/bin/runtreepkg.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/starttreepkgweb.py
===================================================================
--- trunk/bin/starttreepkgweb.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/starttreepkgweb.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,32 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Starts the tree packager webinterface"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.web import runserver
+
+def parse_commandline():
+    parser = create_parser()
+    parser.set_defaults(cherrypy_config=os.path.join(treepkgcmd.topdir,
+                                                     "cherrypy.cfg"))
+    parser.add_option("--cherrypy-config",
+                      help=("The cherrypy config file for the web interface."
+                            " Default cherrypy.cfg"))
+    return parser.parse_args()
+
+def main():
+    options, args = parse_commandline()
+    runserver(options.config_file, options.cherrypy_config)
+
+main()


Property changes on: trunk/bin/starttreepkgweb.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/bin/treepkgcmd.py
===================================================================
--- trunk/bin/treepkgcmd.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/treepkgcmd.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,16 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Initializes the Python interpreter for the treepkg frontends"""
+
+import sys
+import os
+
+# make sure we can import treepkg.  The topdir variable may be
+# referenced by users of this module.
+topdir=os.path.join(os.path.dirname(__file__), os.pardir)
+sys.path.insert(0, topdir)


Property changes on: trunk/bin/treepkgcmd.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/cherrypy.cfg
===================================================================
--- trunk/cherrypy.cfg	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/cherrypy.cfg	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,4 @@
+[global]
+server.socketPort = 9090
+server.threadPool = 10
+server.environment = "production"

Added: trunk/demo.cfg
===================================================================
--- trunk/demo.cfg	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/demo.cfg	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,79 @@
+[DEFAULT]
+# the default section provides defaults for the other sections.  Also,
+# other sections can use the values here in interpolations.
+
+# The basedir to use for interpolations in other sections
+treepkg_dir: /home/builder/enterprise
+
+# The command to use to gain the permissions to execute pbuilder.  The
+# default is sudo.  The actual command line used to run pbuilder is
+# root_cmd followed by the pbuilder command line.  With the default it
+# is something like
+#
+#   sudo pbuilder build <name-of-dsc-file>
+#
+# The value of this option is split into words with the python function
+# shlex.split.  See the python documentation for the precise semantics.
+# shlex.split works pretty much like a POSIX shell but it doesn't to any
+# expansions.
+#
+# Set an empty value to indicate that no special command is needed.
+#
+# You can override this in the pkg_ sections if you need package
+# specific values
+root_cmd: sudo
+
+# The pbuilder config file to use.  It should be an absolute filename.
+# The script initpbuilder.py can create it and the rest of the pbuilder
+# files and directories.  You can override this in the pkg_ sections for
+# individual packagers if necessaary.  You will have to adapt pbuilder
+# yourself, then, though.
+pbuilderrc: %(treepkg_dir)s/pbuilder/pbuilderrc
+
+
+# Email address and name to use as the packager in the debian packages.
+# You can override this in the pkg_ sections if you need package
+# specific values
+deb_email: packager at example.com
+deb_fullname: Sample Packager
+
+
+[treepkg]
+# Section for general tree packager configuration
+
+# Interval in seconds between checks for updates
+check_interval: 300
+
+
+[pkg_kdepim]
+# Sections with names starting with "pkg_" define the configuration for
+# a package.
+
+# The SVN URL to check out for packaging.  Will only be used for the
+# initial checkout
+svn_url: svn://anonsvn.kde.org/home/kde/branches/kdepim/enterprise/kdepim
+
+# The directory under which the packager directory structure will be
+# created.
+base_dir: %(treepkg_dir)s/kdepim
+
+# The packager class defines how the packaging works.  The
+# enterprise.kdepim class knows how to package the enterprise branch of
+# KDE-PIM.
+packager_class: enterprise.kdepim
+
+# Required fields for a pkg_ section inherited from the DEFAULT section
+# in this example: root_cmd deb_email deb_fullname
+
+# An additional option, name, is inferred from the section name.  Its
+# value is the part of the section name after the pkg_ prefix.
+
+
+[pkg_i18n]
+# Another packager.  This one for the the localizations.  This packager
+# requires and additional parameter, orig_tarball.
+svn_url: svn://anonsvn.kde.org/home/kde/branches/kdepim/enterprise/kde-l10n
+base_dir: %(treepkg_dir)s/kde-i18n
+orig_tarball: /home/builder/kde-i18n-de-3.5.5.tar.bz2
+
+packager_class: enterprise.kdei18n

Added: trunk/demostaticweb.cfg
===================================================================
--- trunk/demostaticweb.cfg	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/demostaticweb.cfg	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,42 @@
+# Demo config file for publishstaticweb.py.  The default config file
+# used by publishstaticweb.py is staticweb.cfg, so to use this file as
+# the basis for your configuration, copy or rename this file and adapt
+# it to your needs.
+
+[staticweb]
+# Username and host on which the treepackager runs.  publishstaticweb.py
+# has to be able to connect to that host as the build_user via ssh
+# without knowing the password.  This is best achieved with the
+# ssh-agent.  Also, publishstaticweb.py uses rsync to copy files from
+# build_host to the local host.
+build_user: builder
+build_host: localhost
+
+# the program to run on build_host to create the static web-page.
+# Currently publishstaticweb.py assumes that the default configuration
+# for that program works.
+build_create: ~/treepackager/createstaticweb.py
+
+# the directory on build_host where the static web-site should be put.
+# This value is used as the parameter to the build_create command on
+# build_host.
+build_dir: /tmp/treepkg-web
+
+# Username and host on which to publish the static pages.
+# publishstaticweb.py uses rsync to copy the files from the local cache
+# to the publish_host.
+publish_user: builder
+publish_host: localhost
+
+# the directory on the publish_host where the web-site resides.  It's
+# the directory where the index.html file will be found.  The script may
+# delete files under that directory.
+publish_dir: /tmp/treepkg-status
+
+# local cache directory.  publishstaticweb.py may delete it and its
+# contents.  The value is passed through the python functions
+# os.path.expanduser and os.path.expandvars, so references to home
+# directories of the form ~ or ~user at the beginning of the value and
+# environment variable references of the form $VAR or ${VAR} are
+# expanded.
+cachedir: /tmp/${USER}/treepkg-status-cache

Added: trunk/enterprise/__init__.py
===================================================================


Property changes on: trunk/enterprise/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/enterprise/kdei18n.py
===================================================================
--- trunk/enterprise/kdei18n.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/enterprise/kdei18n.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,139 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import logging
+import shutil
+import time
+
+import treepkg.packager
+import treepkg.util
+import treepkg.run as run
+from treepkg.cmdexpand import cmdexpand
+
+
+class SourcePackager(treepkg.packager.SourcePackager):
+
+    """Creates the debian source package for the i18n files
+
+    This is quite complicated.  The orig.tar.gz file of the debian
+    source package contains one .tar.bz2 file for every language.  Those
+    .tar.bz files are the kde-18n-<lang> files released by the KDE
+    project.  For now, we only have the German localization in the
+    enterprise source package, so the orig.tar.gz file will have the
+    following contents:
+
+      kde-i18n-<version>/
+      kde-i18n-<version>/kde-i18n-de-<version>.tar.bz2
+
+    <version> is the same everywhere.
+
+    The kde-i18n-de tarball contains the localization files for the
+    entire KDE project, including KDE-PIM.  The SVN enterprise branch
+    only contains the localizations for KDE-PIM, though, so we have to
+    assemble a new .tar.bz2 from an original
+    kde-i18n-de-<version>.tar.bz and the new files from the enterprise
+    branch.
+    """
+
+    pkg_basename = "kde-i18n"
+
+    def determine_package_version(self, directory):
+        enterprise_version = (time.strftime("%Y%m%d", time.localtime()) \
+                              + "." + str(self.revision))
+        kdepimversion = "3.5.5"
+        version_template = "%(kdepimversion)s.enterprise.0.%(enterprise_version)s"
+        return version_template % locals()
+
+    def unpack_orig_tarball(self):
+        orig_tarball = self.track.orig_tarball
+        run.call(cmdexpand("tar xjf $tarball -C $directory",
+                           tarball=orig_tarball, directory=self.work_dir))
+        tarbasename = os.path.basename(orig_tarball)
+        splitext = os.path.splitext
+        return os.path.join(self.work_dir,
+                            splitext(splitext(tarbasename)[0])[0])
+
+    def create_i18n_de_tarball(self, pkgbasedir, pkgbaseversion):
+        """Creates a new kde-i18n-de tarball and returns its filename
+
+        This is the tarball as it would be released by KDE.  It is not
+        yet the tarball that will become the .orig.tar.gz for the debian
+        package.
+        """
+        logging.info("Creating kde-i18n-de tarball")
+        untarred_dir = self.unpack_orig_tarball()
+        new_de_dir = os.path.join(pkgbasedir, "new-de")
+        de_dir = os.path.join(pkgbasedir, "de")
+        os.rename(de_dir, new_de_dir)
+        treepkg.util.copytree(untarred_dir, de_dir)
+        treepkg.util.copytree(new_de_dir, de_dir)
+        logging.info("Running scripts/autogen.sh for kde-i18n-de tarball")
+        run.call(cmdexpand("/bin/sh scripts/autogen.sh de"), cwd=pkgbasedir,
+                 suppress_output=True)
+
+        tarballdir = "kde-i18n-de-" + pkgbaseversion
+        os.rename(de_dir, os.path.join(pkgbasedir, tarballdir))
+
+        tarball = os.path.join(os.path.dirname(pkgbasedir),
+                               tarballdir + ".tar.bz2")
+        run.call(cmdexpand("tar cjf $tarball -C $pkgbasedir $tarballdir",
+                           **locals()))
+        logging.info("Created kde-i18n-de tarball")
+        return tarball
+
+    def do_package(self):
+        # Create a new kde-i18n-de tarball from current SVN and the base
+        # kde-i18n-de tarball.
+        pkgbaseversion, pkgbasedir = self.export_sources()
+        tarball = self.create_i18n_de_tarball(pkgbasedir, pkgbaseversion)
+
+        # We have to reuse the same directory when building the
+        # orig.tar.gz.  However, we need to preserver the scripts
+        # sub-directory because it's not needed for the kde-i18n-de
+        # tarball but for the .orig.tar.gz.
+        pkg_scripts_dir = os.path.join(pkgbasedir, "scripts")
+        tmp_scripts_dir = os.path.join(self.work_dir, "scripts")
+        os.rename(pkg_scripts_dir, tmp_scripts_dir)
+        shutil.rmtree(pkgbasedir)
+        os.mkdir(pkgbasedir)
+        os.rename(tmp_scripts_dir, pkg_scripts_dir)
+
+        pkgbasename = self.pkg_basename + "_" + pkgbaseversion
+        origtargz = os.path.join(self.work_dir,
+                                 pkgbasename + ".orig.tar.gz")
+        os.rename(tarball, os.path.join(pkgbasedir,
+                                        os.path.basename(tarball)))
+        self.create_tarball(origtargz, self.work_dir,
+                            os.path.basename(pkgbasedir))
+
+        changemsg = ("Update to SVN enterprise branch rev. %d"
+                     % (self.revision,))
+        self.copy_debian_directory(pkgbasedir, pkgbaseversion,
+                                   changemsg)
+
+        self.create_source_package(pkgbasedir, origtargz)
+        self.move_source_package(pkgbasename)
+
+
+class RevisionPackager(treepkg.packager.RevisionPackager):
+
+    source_packager_cls = SourcePackager
+
+
+class PackageTrack(treepkg.packager.PackageTrack):
+
+    revision_packager_cls = RevisionPackager
+
+    svn_external_subdirs = ["scripts", "scripts/admin"]
+
+    extra_config_desc = ["orig_tarball"]
+
+    def __init__(self, *args, **kw):
+        self.orig_tarball = kw.pop("orig_tarball")
+        super(PackageTrack, self).__init__(*args, **kw)
+


Property changes on: trunk/enterprise/kdei18n.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/enterprise/kdepim.py
===================================================================
--- trunk/enterprise/kdepim.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/enterprise/kdepim.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,82 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Packager that builds KDE-PIM debian packages from the enterprise branch"""
+
+import os
+import time
+import re
+
+import treepkg.util
+import treepkg.packager
+
+
+class SourcePackager(treepkg.packager.SourcePackager):
+
+    pkg_basename = "kdepim"
+
+    def __init__(self, *args, **kw):
+        super(SourcePackager, self).__init__(*args, **kw)
+        self.enterprise_version = (time.strftime("%Y%m%d", time.localtime()) \
+                                   + "." + str(self.revision))
+
+    def kdepim_version(self, directory):
+        """Determine the kdepim version.
+
+        The version is taken from the kdepim.lsm file.
+        """
+        return treepkg.util.extract_lsm_version(os.path.join(directory,
+                                                             "kdepim.lsm"))
+
+    def determine_package_version(self, directory):
+        enterprise_version = self.enterprise_version
+        kdepimversion = self.kdepim_version(directory)
+        version_template = "%(kdepimversion)s.enterprise.0.%(enterprise_version)s"
+        return version_template % locals()
+
+    def update_version_numbers(self, pkgbasedir):
+        """Overrides the inherited method to update version numbers in the code
+        """
+        versionstring = "(enterprise %s)" % self.enterprise_version
+        for versionfile in ["kmail/kmversion.h", "kontact/src/main.cpp",
+                            "korganizer/version.h"]:
+            filename = os.path.join(pkgbasedir, versionfile)
+            patched = re.sub("\(enterprise ([^)]*)\)", versionstring,
+                              open(filename).read())
+            f = open(filename, "w")
+            f.write(patched)
+            f.close()
+
+    def do_package(self):
+        pkgbaseversion, pkgbasedir = self.export_sources()
+        self.update_version_numbers(pkgbasedir)
+
+        pkgbasename = "kdepim_" + pkgbaseversion
+        origtargz = os.path.join(self.work_dir,
+                                 pkgbasename + ".orig.tar.gz")
+        self.create_tarball(origtargz, self.work_dir,
+                            os.path.basename(pkgbasedir))
+
+        changemsg = ("Update to SVN enterprise branch rev. %d"
+                     % (self.revision,))
+        self.copy_debian_directory(pkgbasedir, pkgbaseversion,
+                                   changemsg)
+
+        self.create_source_package(pkgbasedir, origtargz)
+        self.move_source_package(pkgbasename)
+
+
+class RevisionPackager(treepkg.packager.RevisionPackager):
+
+    source_packager_cls = SourcePackager
+
+
+class PackageTrack(treepkg.packager.PackageTrack):
+
+    revision_packager_cls = RevisionPackager
+
+    svn_external_subdirs = ["admin"]


Property changes on: trunk/enterprise/kdepim.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/test/runtests.py
===================================================================
--- trunk/test/runtests.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/runtests.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,72 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""
+Main entry point for the test suite.
+
+Just run this file as a python script to execute all tests
+"""
+
+import os
+import sys
+import unittest
+import optparse
+
+test_dir = os.path.dirname(__file__)
+sys.path.append(os.path.join(test_dir, os.pardir))
+
+def find_test_modules(dirname, package = None):
+    """Return a list the names of the test modules in the directory dirname
+
+    The return value is a list of names that can be passed to
+    unittest.defaultTestLoader.loadTestsFromNames.  Each name of the
+    list is the name of a pure python module, one for each file in
+    dirname whose name starts with 'test' and ends with '.py'.
+
+    The optional parameter package should be the name of the python
+    package whose directory is dirname.  If package is given all names
+    in the returned list will be prefixed with package and a dot.
+    """
+    if package:
+        prefix = package + "."
+    else:
+        prefix = ""
+
+    suffix = ".py"
+    return [prefix + name[:-len(suffix)]
+            for name in os.listdir(dirname)
+                if name.startswith("test") and name.endswith(suffix)]
+
+
+def main():
+    """Run all the tests in the test suite"""
+
+    parser = optparse.OptionParser()
+    parser.set_defaults(verbosity=1)
+    parser.add_option("-v", "--verbose", action="store_const", const=2,
+                      dest="verbosity")
+    opts, rest = parser.parse_args()
+
+    # Build the list of test names.  If names were given on the command
+    # line, run exactly those.  Othwerwise build a default list of
+    # names.
+    if rest:
+        names = rest
+    else:
+        # All Python files starting with 'test' in the current directory
+        # and some directories in Extensions contain test cases.
+        # FIXME: It should be possible to run runtests.py even when not in
+        # the test directory
+        names = find_test_modules(test_dir)
+    suite = unittest.defaultTestLoader.loadTestsFromNames(names)
+    runner = unittest.TextTestRunner(verbosity=opts.verbosity)
+    result = runner.run(suite)
+    sys.exit(not result.wasSuccessful())
+
+
+if __name__ == "__main__":
+    main()


Property changes on: trunk/test/runtests.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/test/test_cmdexpand.py
===================================================================
--- trunk/test/test_cmdexpand.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/test_cmdexpand.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,124 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Tests for the cmdexpand function"""
+
+import unittest
+
+from treepkg.cmdexpand import cmdexpand
+
+
+class TestCMDExpand(unittest.TestCase):
+
+    def test_words(self):
+        """Test cmdexpand with simple whitespace separated words"""
+        self.assertEquals(cmdexpand("abc defg xyz zy"),
+                          ['abc', 'defg', 'xyz', 'zy'])
+
+    def test_single_quoted(self):
+        """Test cmdexpand with some single quoted words"""
+        self.assertEquals(cmdexpand("abc 'defg xyz' zy"),
+                          ['abc', 'defg xyz', 'zy'])
+
+    def test_double_quoted(self):
+        """Test cmdexpand with some double quoted words"""
+        self.assertEquals(cmdexpand('abc "defg  xyz" zy'),
+                          ['abc', 'defg  xyz', 'zy'])
+
+    def test_word_expansion(self):
+        """Test cmdexpand with simple word expansion"""
+        self.assertEquals(cmdexpand('abc $foo ghi', foo="def"),
+                          ['abc', 'def', 'ghi'])
+        self.assertEquals(cmdexpand('abc $foo ghi $bar', foo="def", bar="X"),
+                          ['abc', 'def', 'ghi', 'X'])
+
+    def test_word_expansion_braced_name(self):
+        """Test cmdexpand with word expansion using braced names"""
+        self.assertEquals(cmdexpand('abc ${foo} x${foo}y ghi', foo="def"),
+                          ['abc', 'def', 'xdefy', 'ghi'])
+
+    def test_word_expansion_non_byte_string(self):
+        """Test cmdexpand quoting of dollar signs"""
+        self.assertEquals(cmdexpand('abc $foo $bar ghi', foo=123, bar=u"1 2 3"),
+                          ['abc', '123', '1 2 3', 'ghi'])
+
+    def test_word_expansion_non_identifier(self):
+        """Test cmdexpand word expansion if dollar not followed by identifier"""
+        # $ immediately followed by a non-identifier character
+        self.assertRaises(ValueError, cmdexpand, 'abc $#foo bar', foo="def")
+
+    def test_word_expansion_inside_words(self):
+        """Test cmdexpand word expansions in parts of words"""
+        self.assertEquals(cmdexpand("$foo x$bar y$baz.",
+                                    foo="abc", bar="yz", baz="zx"),
+                          ["abc", "xyz", "yzx."])
+        self.assertEquals(cmdexpand("$foo x$bar-$baz.",
+                                    foo="abc", bar="yz", baz="zx"),
+                          ["abc", "xyz-zx."])
+
+    def test_case_sensitivity(self):
+        """Test case sensitivity of expansion keys"""
+        self.assertEquals(cmdexpand('abc $foo $Foo $FOO',
+                                    foo="def", Foo="DEF", FOO="Def"),
+                          ['abc', 'def', 'DEF', 'Def'])
+
+    def test_list_expansion(self):
+        """Test cmdexpand with list expansion"""
+        self.assertEquals(cmdexpand('abc @foo ghi', foo=["d", "e", "f"]),
+                          ['abc', 'd', 'e', 'f', 'ghi'])
+
+    def test_list_expansion_non_string(self):
+        """Test cmdexpand with list expansion"""
+        self.assertEquals(cmdexpand('abc @foo ghi', foo=[1, 1.0, None]),
+                          ['abc', '1', '1.0', 'None', 'ghi'])
+
+    def test_list_expansion_with_iterators(self):
+        """Test cmdexpand with list expansion using an iterator"""
+        self.assertEquals(cmdexpand('abc @foo ghi',
+                                    foo=(i**2 for i in range(3))),
+                          ['abc', '0', '1', '4', 'ghi'])
+
+    def test_list_expansion_non_identifier(self):
+        """Test cmdexpand with at-sign not followed by identifier"""
+        # @+identifier do not cover entire word
+        self.assertRaises(ValueError, cmdexpand, 'abc @foo, ghi',
+                          foo=["d", "e", "f"])
+
+        # @ immediately followed by a non-identifier character
+        self.assertRaises(ValueError, cmdexpand, 'abc @. z')
+
+    def test_list_expansion_inside_word(self):
+        """Test whether cmdexpand raises ValueError for at-signs inside words"""
+        self.assertRaises(ValueError, cmdexpand, 'abc x at foo ghi',
+                          foo=["d", "e", "f"])
+
+
+    def test_dollar_quoting(self):
+        """Test cmdexpand quoting of dollar signs"""
+        self.assertEquals(cmdexpand('abc $$foo $foo g$$hi', foo="def"),
+                          ['abc', '$foo', 'def', 'g$hi'])
+
+    def test_atsign_quoting(self):
+        """Test cmdexpand quoting of at-signs"""
+        self.assertEquals(cmdexpand('abc @foo $@foo g$@i', foo=["d", "e", "f"]),
+                          ['abc', 'd', 'e', 'f', '@foo', 'g at i'])
+
+    def test_interaction_with_shlex_quoting(self):
+        """Test cmdexpand's interaction with shlex's quoting"""
+        # Unlike unix-shells the expansion isn't influenced much by
+        # shell quoting as supported by shlex.
+        self.assertEquals(cmdexpand('abc "@foo" \'@foo\' ghi',
+                                    foo=["d", "e", "f"]),
+                          ['abc', 'd', 'e', 'f', 'd', 'e', 'f', 'ghi'])
+        self.assertEquals(cmdexpand('abc "$foo" \'$foo\' ghi', foo="def"),
+                          ['abc', 'def', 'def', 'ghi'])
+        self.assertEquals(cmdexpand('abc " $foo" \'a $foo\' ghi', foo="def"),
+                          ['abc', ' def', 'a def', 'ghi'])
+
+
+if __name__ == "__main__":
+    unittest.main()


Property changes on: trunk/test/test_cmdexpand.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/test/test_status.py
===================================================================
--- trunk/test/test_status.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/test_status.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,87 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Tests for the Status classes"""
+
+import os
+import stat
+import unittest
+from datetime import datetime
+
+from treepkg.status import RevisionStatus, Status, EnumFieldDesc
+from treepkg.util import ensure_directory, writefile
+
+
+
+class TestStatus(unittest.TestCase):
+
+    def tempfilename(self):
+        tempdir = os.path.join(os.path.dirname(__file__), "temp")
+        ensure_directory(tempdir)
+        return os.path.join(tempdir, self.id())
+
+    def setUp(self):
+        self.filename = self.tempfilename()
+        if os.path.exists(self.filename):
+            os.remove(self.filename)
+
+    def test_status(self):
+        status = RevisionStatus(self.filename)
+        status.error()
+
+        otherstatus = RevisionStatus(self.filename)
+        self.assertEquals(otherstatus.status.name, "error")
+
+    def test_status_file_permissions(self):
+        status = RevisionStatus(self.filename)
+        status.error()
+
+        mode = os.stat(self.filename).st_mode
+        self.assertEquals(stat.S_IMODE(mode) & 0444, 0444)
+
+    def test_getting_unknown_fields(self):
+        status = RevisionStatus(self.filename)
+        self.assertRaises(AttributeError, getattr, status, "unknown_field")
+
+    def test_setting_unknown_fields(self):
+        status = RevisionStatus(self.filename)
+        self.assertRaises(AttributeError,
+                          setattr, status, "unknown_field", "some value")
+
+    def test_default_values(self):
+        status = RevisionStatus(self.filename)
+        self.assertEquals(status.status.name, "unknown")
+        self.assertEquals(status.start, None)
+        self.assertEquals(status.stop, None)
+
+    def test_date(self):
+        timestamp = datetime(2007, 3, 9, 17, 32, 55)
+        status = RevisionStatus(self.filename)
+        status.start = timestamp
+
+        otherstatus = RevisionStatus(self.filename)
+        self.assertEquals(otherstatus.start, timestamp)
+
+    def test_magic(self):
+        writefile(self.filename,
+                  "Some other magic\nstart: 2007-03-09 17:32:55\n")
+        self.assertRaises(ValueError, RevisionStatus, self.filename)
+
+    def test_status_enum(self):
+
+        class TestStatus(Status):
+            status = EnumFieldDesc()
+            status.add("not_running", "Process is not running",
+                       default=True)
+            status.add("running", "Process is running")
+            status.add("error", "An error occurred")
+
+        status = TestStatus(self.filename)
+        status.running()
+
+        otherstatus = TestStatus(self.filename)
+        self.assertEquals(otherstatus.status.name, "running")


Property changes on: trunk/test/test_status.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/__init__.py
===================================================================
--- trunk/treepkg/__init__.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/__init__.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,6 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.


Property changes on: trunk/treepkg/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/cmdexpand.py
===================================================================
--- trunk/treepkg/cmdexpand.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/cmdexpand.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,116 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Shell like string splitting and expansion"""
+
+import re
+import shlex
+
+
+# helper for the other regular expression matching a python identifier
+match_identifier = "[_a-zA-Z][_a-zA-Z0-9]*"
+
+# regular expression to use for word expansion matching a dollar
+# followed by exactly one of these:
+#  a) another dollar sign or the at-sign (for quoting of these characters)
+#  b) a python identifier
+#  c) a python identifier enclosed in braces
+#  d) something else which indicates invalid use of the dollar sign
+rx_word_expansion = re.compile(r"\$((?P<delim>[$@])"
+                               r"|(?P<named>%(identifier)s)"
+                               r"|\{(?P<braced>%(identifier)s)\}"
+                               r"|(?P<invalid>))"
+                               % dict(identifier=match_identifier))
+
+# regular expression matching an entire word that has to be list
+# expanded.  The regex matches if the word starts with an at-sign.  The
+# part of the word that followes the at-sign either matches an
+# identifier with the named group "named" or anything else which
+# indicates invalid use the at-sign.
+rx_list_expansion = re.compile(r"^@((?P<named>%(identifier)s)|(?P<invalid>.+))$"
+                               % dict(identifier=match_identifier))
+
+# match an unquoted at-sign.
+rx_unquoted_at = re.compile("[^$]@")
+
+def expandword(word, mapping):
+    def replacment(match):
+        key = match.group("named") or match.group("braced")
+        if key:
+            return str(mapping[key])
+
+        delim = match.group("delim")
+        if delim:
+            return delim
+
+        # otherwise invalid has matched and we raise a value error
+        assert match.group("invalid") != None
+        raise ValueError
+
+    return rx_word_expansion.sub(replacment, word)
+
+def cmdexpand(string, **kw):
+    """Split the string into 'words' and expand variable references.
+
+The string is first split into words with shlex.split.  Each of the
+words is then subjected to either word expansion or list expansion.
+Word expansion is very similar to what the Template class in Python's
+string module provides:
+
+  '$$' is expanded to '$'
+
+  '$@' is expanded to '@'
+
+  '$identifier' is expanded to the value of the variable given by
+  identifier.  The identifier has the same syntax as a normal Python
+  identifier.  The identifier stops at the first non-identifier
+  character.  The value is converted to a string with str.
+
+  '${identifier}' is treated like '$identifier' and provides a way to
+  delimit the identifier in cases where the identifier is followed by
+  characters that would otherwise be interpreted as part of the
+  identifier.
+
+A word will remain a single word after the expansion even if the
+expanded string would be treated as multiple words by shlex.
+
+A list expansion is applied to words that consist of a '@' followed by
+an identifier.  Nothing else must be in the word.  The variable the
+identifier refers to must be a sequence and the word will be replaced by
+the sequence with each element of the sequence converted to a string
+with str.
+
+The variables known to the function are the keyword arguments.
+
+Examples:
+
+  >>> from cmdexpand import cmdexpand
+  >>> cmdexpand("ssh $user$@$host", user="john", host="python")
+  ['ssh', 'john at python']
+
+  >>> cmdexpand("scp @files $user$@$host:$remotedir", user="john",
+  ...           host="python", files=["main.py", "cmdexpand.py"],
+  ...           remotedir="/home/john/files")
+  ['scp', 'main.py', 'cmdexpand.py', 'john at python:/home/john/files']
+"""
+    words = shlex.split(string)
+    for index, word in reversed(list(enumerate(words))):
+        match = rx_unquoted_at.search(word)
+        if match:
+            raise ValueError("%r contains an unquoted '@'" % word)
+        match = rx_list_expansion.match(word)
+        if match:
+            key = match.group("named")
+            if key:
+                words[index:index + 1] = (str(item) for item in kw[key])
+            else:
+                assert match.group("invalid") != None
+                raise ValueError("In %r the characters after the '@'"
+                                 " do not match a python identifier" % word)
+        else:
+            words[index] = expandword(word, kw)
+    return words


Property changes on: trunk/treepkg/cmdexpand.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/options.py
===================================================================
--- trunk/treepkg/options.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/options.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,23 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Common command line options"""
+
+import os
+from optparse import OptionParser
+
+
+def create_parser():
+    """Creates an OptionParser with common tree packager options"""
+    parser = OptionParser()
+    dirname = os.path.dirname(__file__)
+    parser.set_defaults(config_file=os.path.join(dirname, os.pardir,
+                                                 "treepkg.cfg"))
+    parser.add_option("--config-file",
+                      help=("The tree packager config file."
+                            " Default treepkg.cfg"))
+    return parser


Property changes on: trunk/treepkg/options.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/packager.py
===================================================================
--- trunk/treepkg/packager.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/packager.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,416 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Classes to automatically build debian packages from subversion checkouts"""
+
+import os
+import time
+import re
+import logging
+import shutil
+import traceback
+import datetime
+
+import util
+import subversion
+import run
+import status
+from cmdexpand import cmdexpand
+
+def _filenameproperty(relative_dir):
+    def get(self):
+        return os.path.join(self.base_dir, relative_dir)
+    return property(get)
+
+
+class SourcePackager(object):
+
+    # Derived classes must supply the package basename
+    pkg_basename = None
+
+    def __init__(self, track, status, work_dir, src_dir, revision):
+        self.track = track
+        self.status = status
+        self.work_dir = work_dir
+        self.src_dir = src_dir
+        self.revision = revision
+        assert(self.pkg_basename)
+
+    def determine_package_version(self, directory):
+        """Returns the version number of the new package as a string
+
+        The directory parameter is the name of the directory containing
+        the newly exported sources.  The sources were exported with the
+        export_sources method.
+
+        The default implementation simply returns the revision converted
+        to a string.
+        """
+        return str(self.revision)
+
+    def export_sources(self):
+        """Export the sources from the subversion working directory
+
+        This method first exports the sources to a temporary directory
+        and then renames the directory.  The new name is of the form
+
+          <pkg_basename>-<version>
+
+        Where pkg_basename is the value of self.pkg_basename and version
+        is the return value of the determine_package_version() method.
+        """
+        temp_dir = os.path.join(self.work_dir, "temp")
+        self.track.export_sources(temp_dir)
+
+        pkgbaseversion = self.determine_package_version(temp_dir)
+        pkgbasedir = os.path.join(self.work_dir,
+                                  self.pkg_basename + "-" + pkgbaseversion)
+
+        os.rename(temp_dir, pkgbasedir)
+        return pkgbaseversion, pkgbasedir
+
+
+    def update_version_numbers(self, pkgbasedir):
+        """Updates the version numbers in the code in pkgbasedir.
+
+        The default implementation does nothing.  Derived classes should
+        override this method if necessary.
+        """
+
+    def create_tarball(self, tarballname, workdir, basedir):
+        """Creates a new tarball.
+
+        Parameters:
+
+          tarballname -- the filename of the new tarball
+          workdir -- The directory into which to change before running tar.
+                     (actually this is done with GNUI tar's -C option)
+          basedir -- The basedirectory of the files that are packaged
+                     into the tarfile.  This should be a relative
+                     filename directly in workdir.
+        """
+        logging.info("Creating tarball %r", tarballname)
+        run.call(cmdexpand("tar czf $tarballname -C $workdir $basedir",
+                           **locals()))
+
+    def copy_debian_directory(self, pkgbasedir, pkgbaseversion, changemsg):
+        """Copies the debian directory and updates the copy's changelog
+
+        Parameter:
+          pkgbasedir -- The directory holding the unpacked source package
+          pkgbaseversion -- The version to update the changelog to
+          changemsg -- The message for the changelog
+
+        When determining the actual version for the new package, this
+        function looks at the previous version in the changelog.  If it
+        has a prefix separated from the version number by a colon this
+        prefix is prepended to the pkgbaseversion parameter.  Debian
+        uses such prefixes for the kde packages.
+        """
+        debian_dir = os.path.join(pkgbasedir, "debian")
+        changelog = os.path.join(debian_dir, "changelog")
+
+        self.track.copy_debian_directory(debian_dir)
+
+        logging.info("Updating %r", changelog)
+        oldversion = util.debian_changelog_version(changelog)
+        if ":" in oldversion:
+            oldversionprefix = oldversion.split(":")[0] + ":"
+        else:
+            oldversionprefix = ""
+        run.call(cmdexpand("debchange -c  $changelog"
+                           " -v ${oldversionprefix}${pkgbaseversion}-kk1"
+                           " $changemsg", **locals()),
+                 env=self.track.debian_environment())
+
+
+    def create_source_package(self, pkgbasedir, origtargz):
+        """Creates a new source package from pkgbasedir and origtargz"""
+        logging.info("Creating new source package")
+        run.call(cmdexpand("dpkg-source -b $directory $tarball",
+                           directory=os.path.basename(pkgbasedir),
+                           tarball=os.path.basename(origtargz)),
+                 cwd=os.path.dirname(pkgbasedir),
+                 suppress_output=True,
+                 env=self.track.debian_environment())
+
+    def move_source_package(self, pkgbasename):
+        """Moves the new source package from the work_dir to the src_dir"""
+        logging.info("Moving source package to %r", self.src_dir)
+        util.ensure_directory(self.src_dir)
+        for filename in [filename for filename in os.listdir(self.work_dir)
+                                  if filename.startswith(pkgbasename)]:
+            os.rename(os.path.join(self.work_dir, filename),
+                      os.path.join(self.src_dir, filename))
+
+    def package(self):
+        """Creates a source package from a subversion checkout.
+
+        After setting up the working directory, this method calls the
+        do_package method to do the actual packaging.  Afterwards the
+        work directory is removed.
+        """
+        util.ensure_directory(self.work_dir)
+        try:
+            self.status.creating_source_package()
+            self.do_package()
+            self.status.source_package_created()
+        finally:
+            logging.info("Removing workdir %r", self.work_dir)
+            shutil.rmtree(self.work_dir)
+
+    def do_package(self):
+        """Does the work of creating a source package
+        This method must be overriden by derived classes.
+
+        The method should do the work in self.work_dir.  When the
+        package is done, the source package files should be in
+        self.src_dir.
+        """
+        raise NotImplementedError
+
+
+class BinaryPackager(object):
+
+    def __init__(self, track, status, binary_dir, dsc_file, logfile):
+        self.track = track
+        self.status = status
+        self.binary_dir = binary_dir
+        self.dsc_file = dsc_file
+        self.logfile = logfile
+
+    def package(self):
+        self.status.creating_binary_package()
+        util.ensure_directory(self.binary_dir)
+        logging.info("Building binary package; logging to %r", self.logfile)
+        run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder build"
+                           " --configfile $pbuilderrc"
+                           " --logfile $logfile --buildresult $bindir $dsc",
+                           rootcmd=self.track.root_cmd,
+                           pbuilderrc=self.track.pbuilderrc,
+                           logfile=self.logfile, bindir=self.binary_dir,
+                           dsc=self.dsc_file),
+                 suppress_output=True)
+        self.status.binary_package_created()
+
+
+class RevisionPackager(object):
+
+    source_packager_cls = SourcePackager
+    binary_packager_cls = BinaryPackager
+
+    def __init__(self, track, revision):
+        self.track = track
+        self.revision = revision
+        self.base_dir = self.track.pkg_dir_for_revision(self.revision, 1)
+        self.status = status.RevisionStatus(os.path.join(self.base_dir,
+                                                         "status"))
+
+    work_dir = _filenameproperty("work")
+    binary_dir = _filenameproperty("binary")
+    src_dir = _filenameproperty("src")
+    build_log = _filenameproperty("build.log")
+
+    def find_dsc_file(self):
+        for filename in os.listdir(self.src_dir):
+            if filename.endswith(".dsc"):
+                return os.path.join(self.src_dir, filename)
+        return None
+
+    def has_build_log(self):
+        return os.path.exists(self.build_log)
+
+    def package(self):
+        try:
+            util.ensure_directory(self.work_dir)
+            self.status.start = datetime.datetime.utcnow()
+            src_packager = self.source_packager_cls(self.track, self.status,
+                                                    self.work_dir, self.src_dir,
+                                                    self.revision)
+            src_packager.package()
+
+            dsc_file = self.find_dsc_file()
+            if dsc_file is None:
+                raise RuntimeError("Cannot find dsc File in %r" % self.src_dir)
+
+            bin_packager = self.binary_packager_cls(self.track, self.status,
+                                                    self.binary_dir, dsc_file,
+                                                    self.build_log)
+            bin_packager.package()
+            self.status.stop = datetime.datetime.utcnow()
+        except:
+            self.status.error()
+            self.status.stop = datetime.datetime.utcnow()
+            raise
+
+    def remove_package_dir(self):
+        logging.info("Removing pkgdir %r", self.base_dir)
+        shutil.rmtree(self.base_dir)
+
+
+class PackageTrack(object):
+
+    revision_packager_cls = RevisionPackager
+
+    svn_external_subdirs = []
+
+    extra_config_desc = []
+
+    def __init__(self, name, base_dir, svn_url, root_cmd, pbuilderrc, deb_email,
+                 deb_fullname, packager_class="treepkg.packager"):
+        self.name = name
+        self.base_dir = base_dir
+        self.svn_url = svn_url
+        self.root_cmd = root_cmd
+        self.pbuilderrc = pbuilderrc
+        self.deb_email = deb_email
+        self.deb_fullname = deb_fullname
+        self.pkg_dir_template = "%(revision)d-%(increment)d"
+        self.pkg_dir_regex \
+                   = re.compile(r"(?P<revision>[0-9]+)-(?P<increment>[0-9]+)$")
+
+    checkout_dir = _filenameproperty("checkout")
+    debian_dir = _filenameproperty("debian")
+    pkg_dir = _filenameproperty("pkg")
+
+    def pkg_dir_for_revision(self, revision, increment):
+        return os.path.join(self.pkg_dir,
+                            self.pkg_dir_template % locals())
+
+    def last_changed_revision(self):
+        revisions = []
+        for directory in [self.checkout_dir] + self.svn_external_subdirs:
+            directory = os.path.join(self.checkout_dir, directory)
+            revisions.append(subversion.last_changed_revision(directory))
+        return max(revisions)
+
+    def get_revision_numbers(self):
+        """Returns a list of the numbers of the packaged revisions"""
+        revisions = []
+        if os.path.exists(self.pkg_dir):
+            for filename in os.listdir(self.pkg_dir):
+                match = self.pkg_dir_regex.match(filename)
+                if match:
+                    revisions.append(int(match.group("revision")))
+        return revisions
+
+    def last_packaged_revision(self):
+        """Returns the revision number of the highest packaged revision.
+
+        If the revision cannot be determined because no already packaged
+        revisions can be found, the function returns -1.
+        """
+        return max([-1] + self.get_revision_numbers())
+
+    def debian_source(self):
+        return util.extract_value_for_key(open(os.path.join(self.debian_dir,
+                                                            "control")),
+                                          "Source:")
+
+    def update_checkout(self):
+        """Updates the working copy of self.svn_url in self.checkout_dir.
+
+        If self.checkout_dir doesn't exist yet, self.svn_url is checked
+        out into that directory.
+        """
+        localdir = self.checkout_dir
+        if os.path.exists(localdir):
+            logging.info("Updating the working copy in %r", localdir)
+            subversion.update(localdir)
+        else:
+            logging.info("The working copy in %r doesn't exist yet."
+                         "  Checking out from %r", localdir,
+                         self.svn_url)
+            subversion.checkout(self.svn_url, localdir)
+
+    def export_sources(self, to_dir):
+        logging.info("Exporting sources for tarball to %r", to_dir)
+        subversion.export(self.checkout_dir, to_dir)
+        # some versions of svn (notably version 1.4.2 shipped with etch)
+        # do export externals such as the admin subdirectory.  We may
+        # have to do that in an extra step.
+        for subdir in self.svn_external_subdirs:
+            absdir = os.path.join(to_dir, subdir)
+            if not os.path.isdir(absdir):
+                subversion.export(os.path.join(self.checkout_dir, subdir),
+                                  absdir)
+
+    def copy_debian_directory(self, to_dir):
+        logging.info("Copying debian directory to %r", to_dir)
+        shutil.copytree(self.debian_dir, to_dir)
+
+    def debian_environment(self):
+        """Returns the environment variables for the debian commands"""
+        env = os.environ.copy()
+        env["DEBFULLNAME"] = self.deb_fullname
+        env["DEBEMAIL"] = self.deb_email
+        return env
+
+    def package_if_updated(self):
+        """Checks if the checkout changed and returns a new packager if so"""
+        self.update_checkout()
+        current_revision = self.last_changed_revision()
+        logging.info("New revision is %d", current_revision)
+        previous_revision = self.last_packaged_revision()
+        logging.info("Previously packaged revision was %d", previous_revision)
+        if current_revision > previous_revision:
+            logging.info("New revision has not been packaged yet")
+            return self.revision_packager_cls(self, current_revision)
+        else:
+            logging.info("New revision already packaged.")
+
+    def get_revisions(self):
+        """Returns RevisionPackager objects for each packaged revision"""
+        return [self.revision_packager_cls(self, revision)
+                for revision in self.get_revision_numbers()]
+
+
+def create_package_track(packager_class, **kw):
+    module = util.import_dotted_name(packager_class)
+    return module.PackageTrack(**kw)
+
+
+class PackagerGroup(object):
+
+    def __init__(self, package_tracks, check_interval):
+        self.package_tracks = package_tracks
+        self.check_interval = check_interval
+
+    def run(self):
+        """Runs the packager group indefinitely"""
+        logging.info("Starting in periodic check mode."
+                     "  Will check every %d seconds", self.check_interval)
+        last_check = -1
+        while 1:
+            now = time.time()
+            if now > last_check + self.check_interval:
+                self.check_package_tracks()
+                last_check = now
+                next_check = now + self.check_interval
+                to_sleep = next_check - time.time()
+                if to_sleep > 0:
+                    logging.info("Next check at %s",
+                                 time.strftime("%Y-%m-%d %H:%M:%S",
+                                               time.localtime(next_check)))
+                    time.sleep(to_sleep)
+                else:
+                    logging.info("Next check now")
+
+    def check_package_tracks(self):
+        logging.info("Checking package tracks")
+        for track in self.package_tracks:
+            try:
+                packager = track.package_if_updated()
+                if packager:
+                    packager.package()
+            except:
+                logging.exception("An error occurred while"
+                                  " checking packager track %r", track.name)
+        logging.info("Checked all package tracks")
+
+    def get_package_tracks(self):
+        return self.package_tracks


Property changes on: trunk/treepkg/packager.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/readconfig.py
===================================================================
--- trunk/treepkg/readconfig.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/readconfig.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,78 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Reads the configuration file"""
+
+import sys
+import shlex
+from ConfigParser import SafeConfigParser, NoOptionError
+
+import util
+
+defaults = dict(root_cmd="sudo")
+
+packager_desc = [
+    "name", "base_dir", "svn_url", "packager_class",
+    ("root_cmd", shlex.split), "pbuilderrc",
+    "deb_email", "deb_fullname",
+    ]
+
+treepkg_desc = [
+    ("check_interval", int),
+    ]
+
+
+def read_config_section(parser, section, item_desc, defaults=None):
+    if defaults is None:
+        defaults = dict()
+    options = dict()
+    for item in item_desc:
+        if isinstance(item, tuple):
+            key, converter = item
+        else:
+            key = item
+            converter = str
+        try:
+            value = parser.get(section, key, vars=defaults)
+            options[key] = converter(value)
+        except NoOptionError:
+            print >>sys.stderr, "Missing option %r in section %r" \
+                  % (key, section)
+            sys.exit(1)
+    return options
+
+
+def read_config(filename):
+    """Reads the tree packager configuration from the file given by filename.
+
+    The function returns a tuple with a ('treepkg') and a list of dicts
+    ('packagers').  The treepkg dict contains the main configuration of
+    the tree packager.  The packagers list contains one dict with the
+    configuratiin for each packager.
+    """
+    parser = SafeConfigParser(defaults)
+    parser.read([filename])
+
+    # extract packager configurations
+    packagers = []
+    for section in parser.sections():
+        if section.startswith("pkg_"):
+            packager_class = parser.get(section, "packager_class")
+            module = util.import_dotted_name(packager_class)
+            desc = packager_desc + module.PackageTrack.extra_config_desc
+            packagers.append(read_config_section(parser, section, desc,
+                                                 dict(name=section[4:])))
+
+    # main config
+    treepkg = read_config_section(parser, "treepkg", treepkg_desc)
+
+    return treepkg, packagers
+
+
+if __name__ == "__main__":
+    import pprint
+    print pprint.pprint(read_config(sys.argv[1]))


Property changes on: trunk/treepkg/readconfig.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/report.py
===================================================================
--- trunk/treepkg/report.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/report.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,89 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Support for status reports"""
+
+import datetime
+
+from packager import create_package_track, PackagerGroup
+from readconfig import read_config
+
+class struct(object):
+
+    """Class to create simple struct like objects
+
+    All keyword arguments passed to the constructor are available as
+    instance variables.
+    """
+
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+    def __repr__(self):
+        fields = ["%s=%r" % item for item in self.__dict__.items()]
+        return "struct(" + ", ".join(fields)  + ")"
+
+
+def get_packager_group(config_file):
+    treepkg_opts, packager_opts = read_config(config_file)
+    return PackagerGroup([create_package_track(**opts)
+                          for opts in packager_opts],
+                         **treepkg_opts)
+
+def status_class(status):
+    """Returns the CSS class for a status"""
+    if status.finished:
+        return "finished"
+    elif status.error:
+        return "error"
+    else:
+        return "inprogress"
+
+def format_time(timestamp):
+    """Formats a datetime object for a status report
+
+    if the argument is true, the return value is simply str applied to
+    the argument, which for datetime objects is a string with the format
+    'YYYY-MM-DD hh:mm:ss'.  If the argument is false, the return value
+    is '<unknown>'.
+    """
+    if timestamp:
+        return timestamp.strftime("%Y-%m-%d %H:%M:%SZ")
+    else:
+        return "<unknown>"
+
+
+def prepare_status(status):
+    return struct(desc=status.status.description,
+                  start=format_time(status.start),
+                  stop=format_time(status.stop),
+                  cls=status_class(status.status))
+
+def prepare_report(group):
+    revisions = {}
+    columns = []
+    tracks = group.get_package_tracks()
+    num_columns = len(tracks)
+    for column, track in enumerate(tracks):
+        columns.append((column, track.name))
+        for revision in track.get_revisions():
+            row = revisions.setdefault(revision.revision, [None] * num_columns)
+            row[column] = struct(revno=revision.revision,
+                                 revision=revision,
+                                 column=column,
+                                 name=track.name,
+                                 status=prepare_status(revision.status))
+
+    # convert the revisions dict into a sorted list of (revno, row)
+    # pairs
+    revisions = revisions.items()
+    revisions.sort()
+    revisions.reverse()
+
+    return struct(columns=columns,
+                  revisions=revisions,
+                  date=format_time(datetime.datetime.utcnow()))


Property changes on: trunk/treepkg/report.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/run.py
===================================================================
--- trunk/treepkg/run.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/run.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,50 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Helper functions to run subprocesses in various ways"""
+
+import os
+import subprocess
+
+
+class SubprocessError(EnvironmentError):
+
+    def __init__(self, command, returncode):
+        EnvironmentError.__init__(self,
+                                  "Command %r finished with return code %d"
+                                  % (command, returncode))
+        self.returncode = returncode
+
+
+def call(command, suppress_output=False, **kw):
+    """Run command as a subprocess and wait until it is finished.
+
+    The command should be given as a list of strings to avoid problems
+    with shell quoting.  If the command exits with a return code other
+    than 0, a SubprocessError is raised.
+    """
+    if suppress_output:
+        kw["stdout"] = open(os.devnull, "w")
+        kw["stderr"] = open(os.devnull, "w")
+    ret = subprocess.call(command, **kw)
+    if ret != 0:
+        raise SubprocessError(command, ret)
+
+
+def capture_output(command, **kw):
+    """Return the stdout and stderr of the command as a string
+
+    The command should be given as a list of strings to avoid problems
+    with shell quoting.  If the command exits with a return code other
+    than 0, a SubprocessError is raised.
+    """
+    proc = subprocess.Popen(command, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT, **kw)
+    output = proc.communicate()[0]
+    if proc.returncode != 0:
+        raise SubprocessError(command, proc.returncode)
+    return output


Property changes on: trunk/treepkg/run.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/status.py
===================================================================
--- trunk/treepkg/status.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/status.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,189 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import datetime
+import time
+
+import util
+
+
+# special object to indicate no default value
+nodefault = object()
+
+
+class FieldDesc(object):
+
+    def __init__(self, default=nodefault):
+        self.default = default
+
+    def has_default(self):
+        return self.default is not nodefault
+
+    def set_default(self, value):
+        self.default = value
+
+    def serialize(self, value):
+        raise NotImplementedError
+
+    def deserialize(self, string):
+        raise NotImplementedError
+
+
+class StringFieldDesc(FieldDesc):
+
+    def serialize(self, value):
+        return str(value)
+
+    def deserialize(self, value):
+        return value.strip()
+
+
+class DateFieldDesc(FieldDesc):
+
+    date_format = "%Y-%m-%d %H:%M:%S"
+
+    def serialize(self, value):
+        return value.strftime(self.date_format)
+
+    def deserialize(self, string):
+        return datetime.datetime(*time.strptime(string.strip(),
+                                                self.date_format)[:6])
+
+
+class EnumValue(object):
+
+    def __init__(self, name, description, finished=False, error=False):
+        self.name = name
+        self.description = description
+        self.finished = finished
+        self.error = error
+
+
+class EnumFieldDesc(FieldDesc):
+
+    def __init__(self, *args, **kw):
+        super(EnumFieldDesc, self).__init__(*args, **kw)
+        self.values = {}
+
+    def add(self, name, description, default=False, **kw):
+        enum = EnumValue(name, description, **kw)
+        self.values[enum.name] = enum
+        if default:
+            self.set_default(enum)
+
+    def __iter__(self):
+        return self.values.itervalues()
+
+    def serialize(self, value):
+        assert value.name is not None
+        return value.name
+
+    def deserialize(self, string):
+        return self.values[string.strip()]
+
+
+def make_setter(fieldname, enum):
+    def setter(self):
+        setattr(self, fieldname, enum)
+    setter.__name__ = enum.name
+    return setter
+
+
+class StatusMetaClass(type):
+
+    def __new__(cls, name, bases, clsdict):
+        # Generate the _fields class variable from the field descriptors
+        # in clsdict and remove the descriptors themselves.  Also, add
+        # one setter method for each enum.
+        fields = dict()
+        for key, value in clsdict.items():
+            if isinstance(value, FieldDesc):
+                fields[key] = value
+                del clsdict[key]
+            if isinstance(value, EnumFieldDesc):
+                for enum in value:
+                    clsdict[enum.name] = make_setter(key, enum)
+        clsdict["_fields"] = fields
+        return type.__new__(cls, name, bases, clsdict)
+
+
+class Status(object):
+
+    __metaclass__ = StatusMetaClass
+
+    # Overwrite in derived classes with a different magic string
+    _magic = "Status 0.0\n"
+
+    # Derived classes may extend a copy of this set with more instance
+    # variables.
+    _attrs = set(["_filename", "_values"])
+
+    def __init__(self, filename):
+        assert os.path.isabs(filename)
+        self._filename = filename
+        self.read()
+
+    def _init_values(self):
+        self._values = {}
+
+    def read(self):
+        self._init_values()
+        if not os.path.exists(self._filename):
+            return
+        f = open(self._filename)
+        try:
+            magic = f.next()
+            if magic != self._magic:
+                raise ValueError("File %r has wrong magic" % self._filename)
+            for line in f:
+                field, value = line.split(":", 1)
+                self._values[field] = self._fields[field].deserialize(value)
+        finally:
+            f.close()
+
+    def write(self):
+        lines = [self._magic]
+        for field, desc in self._fields.items():
+            if field in self._values:
+                lines.append("%s: %s\n"
+                             % (field, desc.serialize(self._values[field])))
+        util.writefile(self._filename, "".join(lines), 0644)
+
+    def __getattr__(self, attr):
+        desc = self._fields.get(attr)
+        if desc is not None:
+            if attr in self._values:
+                return self._values[attr]
+            elif desc.has_default():
+                return desc.default
+        raise AttributeError(attr)
+
+    def __setattr__(self, attr, value):
+        if attr in self._fields:
+            self._values[attr] = value
+            self.write()
+        elif attr in self._attrs:
+            self.__dict__[attr] = value
+        else:
+            raise AttributeError(attr)
+
+
+class RevisionStatus(Status):
+
+    _magic = "TreePackagerStatus 0.0\n"
+
+    status = EnumFieldDesc()
+    status.add("creating_source_package", "creating source package")
+    status.add("source_package_created", "source package created")
+    status.add("creating_binary_package", "building binary packages")
+    status.add("binary_package_created", "build successful", finished=True)
+    status.add("error", "error", error=True)
+    status.add("unknown", "unknown", default=True)
+
+    start = DateFieldDesc(default=None)
+    stop = DateFieldDesc(default=None)


Property changes on: trunk/treepkg/status.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/subversion.py
===================================================================
--- trunk/treepkg/subversion.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/subversion.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,40 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Collection of subversion utility functions"""
+
+import os
+
+import run
+from cmdexpand import cmdexpand
+from util import extract_value_for_key
+
+
+def checkout(url, localdir):
+    """Runs svn to checkout the repository at url into the localdir"""
+    run.call(cmdexpand("svn checkout -q $url $localdir", **locals()))
+
+def update(localdir):
+    """Runs svn update on the localdir"""
+    run.call(cmdexpand("svn update -q $localdir", **locals()))
+
+def export(src, dest):
+    """Runs svn export src dest"""
+    run.call(cmdexpand("svn export -q $src $dest", **locals()))
+
+def last_changed_revision(svn_working_copy):
+    """return the last changed revision of an SVN working copy as an int"""
+    # Make sure we run svn under the C locale to avoid localized
+    # messages
+    env = os.environ.copy()
+    env["LANG"] = "C"
+
+    output = run.capture_output(cmdexpand("svn info $svn_working_copy",
+                                          **locals()),
+                                env=env)
+    return int(extract_value_for_key(output.splitlines(),
+                                     "Last Changed Rev:"))


Property changes on: trunk/treepkg/subversion.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/util.py
===================================================================
--- trunk/treepkg/util.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/util.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,133 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Collection of functions that didn't fit elsewhere"""
+
+import os
+import tempfile
+import shutil
+
+import run
+
+
+def import_dotted_name(dotted_name):
+    module = __import__(dotted_name)
+    for name in dotted_name.split(".")[1:]:
+        module = getattr(module, name)
+    return module
+
+def extract_value_for_key(lines, key):
+    """Parses a sequence of strings for a key and returns the associated value
+
+    The function determines the first string in lines that starts with
+    key.  It returns the rest of the lines stripped of leading and
+    trailing whitespace.
+    """
+    for line in lines:
+        if line.startswith(key):
+            return line[len(key):].strip()
+
+def extract_lsm_version(lsm_file):
+    return extract_value_for_key(open(lsm_file), "Version:")
+
+def debian_changelog_version(changelog):
+    """Returns the newest version in a debian changelog."""
+    output = run.capture_output(["dpkg-parsechangelog",  "-l" + changelog])
+    return extract_value_for_key(output.splitlines(), "Version:")
+
+
+def ensure_directory(directory):
+    """Creates directory and all its parents.
+
+    Unlike os.makedirs, this function doesn't throw an exception
+    """
+    if not os.path.isdir(directory):
+        os.makedirs(directory)
+
+
+def copytree(src, dst, symlinks=False):
+    """Recursively copy a directory tree using copy2().
+
+    This version is basically the same as the one in the shutil module
+    in the python standard library, however, it's OK if the destination
+    directory already exists.
+
+    If the optional symlinks flag is true, symbolic links in the
+    source tree result in symbolic links in the destination tree; if
+    it is false, the contents of the files pointed to by symbolic
+    links are copied.
+    """
+    names = os.listdir(src)
+    ensure_directory(dst)
+    errors = []
+    for name in names:
+        srcname = os.path.join(src, name)
+        dstname = os.path.join(dst, name)
+        try:
+            if symlinks and os.path.islink(srcname):
+                linkto = os.readlink(srcname)
+                os.symlink(linkto, dstname)
+            elif os.path.isdir(srcname):
+                copytree(srcname, dstname, symlinks)
+            else:
+                shutil.copy2(srcname, dstname)
+            # XXX What about devices, sockets etc.?
+        except (IOError, os.error), why:
+            errors.append((srcname, dstname, why))
+    if errors:
+        raise Error, errors
+
+
+
+def writefile(filename, contents, permissions=None):
+    """Write contents to filename in an atomic way.
+
+    The contents are first written to a temporary file in the same
+    directory as filename.  Once the contents are written, the temporary
+    file is closed and renamed to filename.
+
+    The optional parameter permissions, if given, are the permissions
+    for the new file.  By default, or if the parameter is None, the
+    default permissions set by the tempfile.mkstemp are used which means
+    that the file is only readable for the user that created the file.
+    The permissions value is used as the second parameter to os.chmod.
+    """
+    dirname, basename = os.path.split(filename)
+    fileno, tempname = tempfile.mkstemp("", basename, dirname)
+    try:
+        os.write(fileno, contents)
+        if not contents.endswith("\n"):
+            os.write(fileno, "\n")
+        os.close(fileno)
+        if permissions is not None:
+            os.chmod(tempname, permissions)
+        os.rename(tempname, filename)
+    finally:
+        if os.path.exists(tempname):
+            os.remove(tempname)
+
+
+class StatusFile(object):
+
+    def __init__(self, filename):
+        assert os.path.isabs(filename)
+        self.filename = filename
+
+    def read(self):
+        f = open(self.filename)
+        try:
+            self.status = f.read()
+        finally:
+            f.close()
+
+    def set(self, status, extra=""):
+        ensure_directory(os.path.dirname(self.filename))
+        writefile(self.filename, status + "\n" + extra)
+
+    def get(self):
+        self.read()
+        return self.status.strip()


Property changes on: trunk/treepkg/util.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/treepkg/web-status.html
===================================================================
--- trunk/treepkg/web-status.html	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/web-status.html	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,55 @@
+<html xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title>Tree Packager Status</title>
+    <style type="text/css">
+      .statustable { background:#F4F4F4; }
+      .statustablehead { background:#E0E0E0; }
+      .statusheading { font-weight:bold; }
+      .finished { background:#C0FFC0; }
+      .inprogress { background:#FFFFC0; }
+      .error { background:#FFC0C0; }
+      td { padding:5px; background:#FFFFFF}
+    </style>
+  </head>
+  <body>
+    <h1>Tree Packager Status</h1>
+
+    <table class="statustable">
+      <tr>
+	<th class="statustablehead">Revision</th>
+	<py:for each="col in report.columns">
+	  <th class="statustablehead">${col[1]}</th>
+	</py:for>
+      </tr>
+
+      <py:for each="row in report.revisions">
+	<tr>
+	  <td>${row[0]}</td>
+	  <py:for each="col in row[1]">
+	    <py:choose>
+	      <py:when test="col">
+		<td class="${col.status.cls}">
+		  <span class="statusheading">${col.status.desc}</span><br/>
+		  Start: ${col.status.start}<br/>
+		  Stop: ${col.status.stop}<br/>
+		  <py:if test="col.revision.has_build_log">
+		    <a href="${col.name}/${col.revno}/build_log.txt">build log</a>
+		  </py:if>
+		</td>
+	      </py:when>
+	      <py:otherwise>
+		<td></td>
+	      </py:otherwise>
+	    </py:choose>
+	  </py:for>
+
+	</tr>
+      </py:for>
+    </table>
+
+    <hr/>
+    report generated at ${report.date},
+    powered by <a href="http://treepkg.wald.intevation.org/">Tree Packager</a>
+
+  </body>
+</html>

Added: trunk/treepkg/web.py
===================================================================
--- trunk/treepkg/web.py	2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/web.py	2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,97 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import shutil
+
+from genshi.template import TemplateLoader
+
+import cherrypy
+from cherrypy import expose
+from cherrypy.lib import cptools
+
+import report
+
+
+class Status(object):
+
+    """Implements the tree packager status pages"""
+
+    def __init__(self, treepkg_config):
+        self.treepkg_config = treepkg_config
+        self.loader = TemplateLoader([os.path.dirname(__file__)])
+
+    @expose
+    def index(self):
+        group = report.get_packager_group(self.treepkg_config)
+        tmpl = self.loader.load('web-status.html')
+        stream = tmpl.generate(report=report.prepare_report(group))
+        return stream.render('html')
+
+    def build_log_filename(self, package_track_name, revno):
+        """Returns the name of the build log file of a revision if it exists"""
+        group = report.get_packager_group(self.treepkg_config)
+        for track in group.get_package_tracks():
+            if track.name == package_track_name:
+                for revision in track.get_revisions():
+                    if str(revision.revision) == revno:
+                        if revision.has_build_log():
+                            return revision.build_log
+
+    @expose
+    def default(self, *rest):
+        """Handles requests for .../pkg/revno/build_log.txt"""
+        filename = None
+        if len(rest) == 3 and rest[2] == "build_log.txt":
+            filename = self.build_log_filename(*rest[:2])
+        if filename is not None:
+            return cptools.serveFile(filename, contentType="text/plain")
+        else:
+            raise cherrypy.HTTPError(status="404")
+
+    def create_static_site(self, destdir):
+        """Creates a static web-page under destdir"""
+        # make sure we have an empty destdir
+        shutil.rmtree(destdir, True)
+        os.mkdir(destdir)
+
+        # create the index file
+        f = open(os.path.join(destdir, "index.html"), "wt")
+        f.write(self.index())
+        f.close()
+
+        # symlink the build-logs
+        group = report.get_packager_group(self.treepkg_config)
+        for track in group.get_package_tracks():
+            trackdir = os.path.join(destdir, track.name)
+            for revision in track.get_revisions():
+                revdir = os.path.join(trackdir, str(revision.revision))
+                if revision.has_build_log():
+                    if not os.path.isdir(trackdir):
+                        os.mkdir(trackdir)
+                    os.mkdir(revdir)
+                    os.symlink(revision.build_log,
+                               os.path.join(revdir, "build_log.txt"))
+
+
+
+
+class TreePKG(object):
+
+    """Root object for the tree packager web interface"""
+
+    @expose
+    def index(self):
+        raise cherrypy.HTTPRedirect('/status')
+
+
+def runserver(treepkg_config, cherrypy_config):
+    cherrypy.root = TreePKG()
+    cherrypy.root.status = Status(treepkg_config=treepkg_config)
+
+    cherrypy.config.update(file=cherrypy_config)
+    cherrypy.server.start()


Property changes on: trunk/treepkg/web.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native



More information about the Treepkg-commits mailing list