Commits

Anonymous committed 4a7bee7

Taken from PyJobTransforms

Comments (0)

Files changed (19)

+6-03-2006 Martin Woudstra
+	Renamed package from PyJobTransforms to PyJobTransformsCore
+	Moved examples to the respective domain packages
+	cmt/requirements: 
+	      - Installing jobtransforms in dedicated area (InstallArea/trfs)
+	      -	Adding trfs installarea path to PYTHONPATH,PATH & JOBOPTSEARCHPATH
+3-03-2006 Martin Woudstra
+	cmt/expand_files.py: new! Utility to expand filenames from source & destination dir
+	cmt/requirements: using new utility expand_files.py to gather all transforms
+	cmt/requirements: default directory for finding jobtransforms is ../scripts
+2-03-2006 Martin Woudstra
+	python/trfutil.py: fixed bug in Author.__str__()
+	python/runargs.py: added function RunArguments.__str__()
+1-03-2006 Martin Woudstra
+	python/trfutil.py: load_module() more rubust
+	test/trfutil_test.py: some more tests for load_module() and class StringNumberList
+28-02-2006 Martin Woudstra
+	test/myunittest.py: now handles temporary files
+	test/MyTest.py: renamed to myunittest.py
+	test/basic_trfarg.py: now using myunittest.py
+	example/dc3_simul_trf.py: new! Jobtransform for simulation (not finished yet)
+27-02-2006 Martin Woudstra
+	test/*test.py: fixed some bugs
+	test/MyTest.py: new! base class for unit testing
+	python/basic_trfarg.py: bugfix in InputTarFileArg
+	python/trfenv.py: bugfix in setup_runtime()
+23-02-2006 Martin Woudstra
+	cmt/requirements: added pattern declare_jobtransforms
+	python/trfargs: moved all files in this directory up one level. This dir is now obsolete.
+	python/*.py: adapted for new location of files that were in subdir trfargs
+17-02-2006 Martin Woudstra
+	python/trfenv.py: added function setup_runtime() that must be called explicitly,
+	                  otherwise os.system() can not do any command without the athena full 
+	                  runtime environment (I guess because of LD_PRELOAD)
+	python/trf.py: calling trfenv.setup_runtime() explicitly before running athena
+	python/trfutil.py: - added class StringNumList, which does the FileList decoding.
+	                   - class Filename now allows regexp as type,contents and suffix.
+	python/trfargs/basic_trfarg.py:
+	                   - moved intelligence of FileListArg to class StringNumberList
+			   - added class InputTarFileArg
+	test/filelist_test.py: removed. Contents moved to trfutil_test.py
+	test/load_test.py: removed. Contents moved to trfutil_test.py
+	test/trfutil_test.py: new! gathers all unittests for trfutil.py
+	test/basic_trfarg_test.py: new! gathers all unittests for basic_trfarg.py
+16-02-2006 Martin Woudstra
+	test/filelist_test.py: new! Unittest for testing FileListArg
+	python/trfargs/basic_trfarg.py: debugged FileListArg.
+14-02-2006 Martin Woudstra
+	python/trfargs/basic_trfarg.py: added classes FileListArg and InputFileListArg. Not yet tested.
+	python.trferr.py: cosmetic change: don't print ExitStatus to screen if status 0 (appeared in help)
+10-02-2006 Martin Woudstra
+	python/trferr.py: added class TransformErrorHandler to handle errors
+	python/athena_wrapper.py: moved error handling to external error handler
+	python/trfutil.py: added load_module() and load_errorhandlers() functions
+08-02-2006 Martin Woudstra
+	python/*.py: improve error reporting in case a DLL is not found
+01-02-2006 Martin Woudstra
+	python/trf.py: - simplified member function names
+	               - made data members private
+	               - removed obsolete *Instance* member functions
+	               - removed auto-skeleton generation (was not ready anyway)
+		       - remove usage of Pre/PostRunCommand classes
+	python/trfutil.py:
+	               - simplified member function names
+	               - made class data members private and changed some names to avoid clashes
+		       - removed overkill classes Pre/PostRunCommand
+	python/trfxml.py: minor improvements
+	python/trfargs/basic_trfarg.py: 
+	               - simplified member function names
+	               - made data members private
+		       - changed member function names so type() and metaType() now correspond to the Production System naming scheme
+		       - moved fully specified argument classes to module full_trfarg.py
+	python/trfargs/full_trfarg.py: New! Contains fully specified argument classes previously in basic_trfarg.py
+	python/example/ratxml.py: adapted to changes in other python files
+	python/example/dc3_evgen_trf.py: using full_trfarg module instead of basic_trfarg (new scheme)
+	test/ : New directory to hold tests
+	test/testargs_trf.py: New! First TestJobTransform
+31-01-2006 Martin Woudstra
+	python/trf.py: Removed option -q xmlsig. Functionality now in external python script (example/ratxml.py)
+	python/trfutil.py: added function load_transforms() to load all transformations available in a python file.
+	python/trfxml.py: some improvements
+	python/trfargs/basic_trfargs.py: added classes ListArg and SkipEventsArg
+	example/ratxml.py: New! Takes over the functionality of option -q xmlsig, to move the maintainance of this code
+	                   to where it belongs: in the RAT group  (since they use this functionality).
+30-01-2006 Martin Woudstra
+	python/trf.py: - First working version of -q,--query xmlsig
+	python/trfutil.py: added some more file classes. Can specify type,contents and suffix.
+	python/athena_wrapper.py: added catching of exception AttributeError
+	python/trfxml.py: New! Some xml utilities
+	python/trfargs/basic_trfargs.py: added some specific input and output classes. Check on filename suffix.
+	example/dc3_evgen_trf.py: using new input/output file classes
+	example/skeleton.dc3_evgen.py: adapted to use new variable name of input file.
+27-01-2006 Martin Woudstra
+	python/trf.py: - removed option -r,--run (obsolete)
+		       - added option -q,--query xmlsig: print arguments signature in xml format for production system.
+	                 Work in progress...
+	python/trfutil.py: started to add some specific file type classes
+	python/trferr.py: add default for call to trferr.exit()
+26-01-2006 Martin Woudstra
+	python/athena_wrapper.py: Added catching of exception NameError
+	python/trferr.py: added JOBOPTIONS_PYTHON_ERROR
+	python/trf.py: - writing argument values in object 'runArgs' instead of global namespace
+	               - parameters filename renamed to runargs.<trfname>.py
+		       - write out last full command line in file 'last.<trfname>' for easy repeating
+		       - easier to read help output (-h option)
+		       - some cleanup of code
+	python/trfutil.py: removed classes JobOptionsSnippet and JobOptionsFile
+	python/trfargs/runargs.py: New! Holds the RunArgument class
+	example/dc3_evgen_trf.py: removed the default values
+	example/skeleton.dc3_evgen.py: Adapted to use run arguments from runArgs object
+24-01-2006 Martin Woudstra
+	python/athena_wrapper.py: Catch exception SystemExit
+	python/trf.py: some bug fixes
+	python/trferr.py: added class OutputFileError
+	python/trfargs/basic_trfarg.py: modified Pre/PostRunActions to return message string
+	python/trfutil.py: modified Pre/PostRunActions to return message string
+	example/dc3_evgen_trf.py: added defaults for ntuple and histo files (for testing)
+	example/skeleton.dc3_evgen.py: Fixed bugs. Runs now. Ntuple file missing, Histo file empty.
+23-01-2006 Martin Woudstra
+	python/trf.py: improve options handling
+	python/trfenv.py: add LD_PRELOAD environment to run athena_wrapper.py
+	python/athena_wrapper.py: added catching of "DLL not found" error
+	example/skeleton.dc3_evgen.py: new! Added to get something to work
+
+20-01-2006 Martin Woudstra
+	cmt/requirements: fix package name and make consistent with project builds
+	example: now subclassing event generation example transformation
+	python/trf.py: Added 'running athena' part, added exit status values, cleanup
+	python/trferr.py: added error codes & messages (values to be changed)
+	python/trfutil.py: added find_joboptions
+	python/athena_wrapper.py: new! Turn Include exception into exit code
+	python/trfenv.py: new! Set up some environment info
+
+24-11-2005 Martin Woudstra
+	First import of some real code. Only partly working.

cmt/expand_files.py

+#!/usr/bin/env python
+# usage: expand_files [options] [files] [options] [files]
+# Return a list of filenames with the package name prepended
+# Files will be collected from source dirs, and prepended with the
+# destination dir.
+# Options:
+# -r=<rootdir>: Root directory of the package. Is used to search for the
+#           files in the correct package.
+# -s=<srcdir>: Default source directory. Will be used if no directory is
+#          specified before a filename. This option can be inserted
+#          in between the filenames.
+# -d=<destdir>: destination directory. Will be prepended to each filename.
+#          Can be inserted in between the filenames. The last one before
+#          a filename will be used.
+import sys,os,glob
+srcdir = os.curdir
+destdir = ''
+rootdir=''
+files=[]
+for args in sys.argv[1:]:
+    for arg in args.split():
+        if arg.startswith('-s='):
+            # get a new default directory
+            srcdir=arg[3:]
+        elif arg.startswith('-d='):
+            destdir=arg[3:]
+        elif arg.startswith('-r='):
+            rootdir=arg[3:]
+        else:
+            argdir = os.path.dirname(arg)
+            if not argdir: argdir = srcdir
+            argname = os.path.basename(arg)
+            fullpath = os.path.normpath( os.path.join( rootdir,'cmt',argdir,argname) )
+            filelist = glob.glob( fullpath )
+            files += [ os.path.join(destdir,os.path.basename(f)) \
+                       for f in filelist ]
+
+if files:
+    print ' '.join( files )
+package PyJobTransformsCore
+author Martin Woudstra <Martin.Woudstra@cern.ch>
+
+use AtlasPolicy AtlasPolicy-01-* 
+
+use AtlasPython AtlasPython-* External -no_auto_imports
+# for some python tools
+#use AthenaCommon AthenaCommon-02-* Control -no_auto_imports
+
+apply_pattern declare_python_modules files="*.py"
+
+# Pattern to declare python jobtransforms.
+# Each jobtransform normally has 2 components:
+#    - The python script (*_trf.py) defining the trf
+#    - The corresponding skeleton joboptions file(s)
+# The pattern  takes 2 arguments:
+# trfs = list of jobtransforms, by default taken from ../scripts
+#        It will be installed in the Installarea/${trfs_installdir}.
+# jo = list of skeleton joboptions files belonging to the jobtransforms (usually one).
+#      By default taken from ../share
+#       It will be installed in the Installarea/${trfs_installdir}/jobOptions
+#
+macro expand_files_cmd ${PYJOBTRANSFORMSCOREROOT}/cmt/expand_files.py
+
+macro trfs_installdir 'trfs'
+
+pattern declare_jobtransforms \
+	apply_pattern generic_declare_for_link kind=trfs   files='-s=../scripts <trfs>' prefix=${trfs_installdir} name=Trf ; \
+	apply_pattern generic_declare_for_link kind=trf_jo files='-s=../share <jo>' prefix="${trfs_installdir}/jobOptions" name=Trf ; \
+	macro <package>_jobtransforms "`${expand_files_cmd} -r=$(<PACKAGE>ROOT) -s=../scripts <trfs>`" ; \
+	macro_append all_jobtransforms " ${<package>_jobtransforms} " ; \
+
+
+# the use of CMTINSTALLAREA macro below is not documented anywhere, but it works.
+# Doing the path* and set* commands outside of a cmtpath_pattern does not work, since the
+# one in AtlasPolicy removes everything I set here, and is apparently called after the
+# 'normal' path* and set* commands.
+cmtpath_pattern \
+	path_remove  PYTHONPATH "/$(trfs_installdir)" ; \
+	path_prepend PYTHONPATH "$(CMTINSTALLAREA)/$(trfs_installdir)" ; \
+	path_remove  PATH  "/$(trfs_installdir)" ; \
+	path_prepend PATH "$(CMTINSTALLAREA)/$(trfs_installdir)" ; \
+	set_remove  JOBOPTSEARCHPATH "/$(trfs_installdir)" ; \
+	set_prepend JOBOPTSEARCHPATH ",$(CMTINSTALLAREA)/$(trfs_installdir)/jobOptions"
+
+

example/ratxml.py

+#!/usr/bin/env python
+import os,sys
+from PyJobTransforms.trfutil import load_transforms
+
+indent = "   "
+
+def opentag(tag,**nargs):
+    otag = '<' + tag
+    for key in nargs.keys(): otag += ' %s=\"%s\"' % (key,nargs[key])
+    otag += '>'
+    return otag
+
+
+def closetag(tag):
+    return '</%s>' % tag
+
+
+def simplefield(tag,value,**nargs):
+    return opentag(tag,**nargs) + str(value) + closetag(tag)
+
+
+def compositefield(tag,fields,**nargs):
+    sep = os.linesep + indent
+    field = opentag(tag,**nargs) + sep
+    field += sep.join( [ f.replace(os.linesep, sep) for f in fields ] )
+    return field + os.linesep + closetag(tag)
+
+
+
+def arg_xml( arg ):
+    fields = [ simplefield( 'name', arg.name()) ,
+               simplefield( 'position', arg.position() ) ,
+               simplefield( 'level', arg.jobOrTask() ) ,
+               simplefield( 'type', arg.type() ) ,
+               simplefield( 'metatype', arg.metaType() ) ,
+               simplefield( 'doc', arg.help() ) ]
+
+    return compositefield('jobtransformalpar', fields)
+        
+
+def trf_xml(trf):
+    sig = [ arg_xml(arg) for arg in trf.argumentList() ] 
+    return compositefield( 'jobtransform', sig, name=trf.name(), version=trf.version() )
+
+
+def do_ratxml( trf_py ):
+    for trf in load_transforms( trf_py ): print trf_xml(trf)
+
+
+# print usage when no arguments are given
+if len(sys.argv) == 1:
+    print 'usage: %s <python_file> [python_file]' % sys.argv[0]
+    print 'Prints on screen an xml signature of the arguments for all JobTransform instantiations found in all python files on the command line'
+    sys.exit()
+
+# execute it!
+for arg in sys.argv[1:]: do_ratxml( arg )
+    

python/__init__.py

Empty file added.

python/athena_wrapper.py

+#!/usr/bin/env python
+import os,sys
+import exceptions
+from AthenaCommon.Include import IncludeError
+from AthenaCommon.Utils.unixtools import which
+
+from PyJobTransformsCore import trferr
+from PyJobTransformsCore import trfenv
+
+try:
+#    print '%s=%s' % (trfenv.PYTHONPATH, os.environ[trfenv.PYTHONPATH])
+#    print '%s=%s' % (trfenv.LD_LIBRARY_PATH, os.environ[trfenv.LD_LIBRARY_PATH])
+    sys.argv[0] = trfenv.athena_py
+    athena_exe = which(trfenv.athena_py)
+    if not athena_exe:
+        trferr.exit( trferr.TRF_ENVIRONMENT_ERROR, '%s not found' % (trfenv.athena_py) )
+    execfile(athena_exe)
+
+#
+# athena exited with a sys.exit()
+#
+except exceptions.SystemExit, e:
+    status = e.args[0]
+    if status == 0:
+        sys.exit(0) # don't write exitstatus file
+    else:
+        trferr.exit( trferr.ATHENA_FAILURE_ERROR, 'SystemExit %d' % status )  
+
+#
+# A number of errors that occur in the joboptions files
+#
+except IncludeError, e:
+    trferr.exit( trferr.JOBOPTIONS_NOT_FOUND, e.args )
+
+except exceptions.NameError, e:
+    trferr.exit( trferr.JOBOPTIONS_PYTHON_ERROR, '%s: %s' % (e.__class__.__name__, e.args) )
+    
+except exceptions.AttributeError, e:
+    trferr.exit( trferr.JOBOPTIONS_PYTHON_ERROR, '%s: %s' % (e.__class__.__name__, e.args) )
+
+
+#
+# Any other exceptions derived from the python exception base class
+#
+except exceptions.Exception, e:
+    trferr.errorHandler.handleAthenaException(e)
+##     args0 = e.args[0]
+##     argType0 = type(args0)
+##     # test for some known strings
+##     if argType0 == type(''):
+##         if args0.find('Failed to load DLL') != -1:
+##             # try to find the guilty one
+##             mess = ""
+##             dllRE = "^theApp.Dlls\s*[+]?="
+##             stack = find_in_stack( dllRE )
+##             if stack:
+##                 text = stack[TRACEBACK_TEXT]
+##                 dllNameRE = "([\w\.\-]+)"
+##                 subRE = "%s%s%s%s" % (dllRE,r"\s*\[\s*\"", dllNameRE, r"\"\s*\]")
+##                 #print 'subRE_stack=%s' % subRE
+##                 dll = re.sub( subRE, r"\1", text )
+##                 lib = 'lib%s.so' % (dll)
+##                 full_lib = find_library(lib)
+##                 mess = 'module %s can not be loaded' % (dll)
+##                 if not full_lib:
+##                     mess = ' because %s can not found in LD_LIBRARY_PATH' % (lib)
+##                 else:
+##                     lddOut = os.popen( 'echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"; ldd %s' % (full_lib) )
+##                     missLibs = [ ]
+##                     subRE = "%s%s%s" % (r"^\s*",dllNameRE,r"\s+.*not found\s*.*$")
+##                     #print 'subRE_ldd=%s' % subRE
+##                     for line in lddOut:
+##                         if re.search( "not found", line ):
+##                             misLib = re.sub( subRE, r"\1", line )
+##                             missLibs.append( misLib )
+##                             if find_library(misLib):
+##                                 print "I find %s, but ldd does not..." % misLib
+##                         elif re.search("LD_LIBRARY_PATH=", line ):
+##                             print line
+                            
+##                     lddOut.close()
+##                     if len(missLibs) >= 1:
+##                         mess += ' because %s can not found in LD_LIBRARY_PATH' % \
+##                                 (', '.join(missLibs))
+                        
+                    
+##             trferr.exit( trferr.ATHENA_LOAD_DLL_ERROR, mess )
+
+##     # if I get here, it's an unknown exception
+##     trferr.exit( trferr.UNKNOWN_EXCEPTION_RAISED, '%s: %s' % (e.__class__.__name__ ,e.args) )
+
+#
+# If all else fails...
+#
+else:
+    trferr.exit( trferr.UNKNOWN_EXCEPTION_RAISED, '%s: %s' % (e.__class__.__name__ ,e.args) )
+    

python/basic_trfarg.py

+################################################################################
+# This file contains the basic argument types for JobTransforms.
+# They can not be used directly as JobTransform arguments.
+# Classes defined in module full_trfarg.py can be used in JobTransforms. 
+# See the doc of the Argument class for more information.
+################################################################################
+
+import os,tarfile
+
+from AthenaCommon.Logging import logging
+
+from PyJobTransformsCore.trfutil import *
+from PyJobTransformsCore.trferr import *
+
+#################################################################################
+# Argument base class
+################################################################################
+class Argument:
+    """Argument base class specifying basic properties of a command line argument.
+Only fully specified argument classes can be used in a JobTransform implementation.
+A fully specified argument should derive from one of the basic argument classes
+defined in this file, and should override the member function isFullArgument(self),
+which should return True."""
+    
+    def __init__(self,help,name):
+        """help    : short help string to help user fill in the argument
+       name    : name of the argument (string)."""
+        if help == 'default': help = self.defaultHelp()
+        if name == 'default': name = self.defaultName()
+        self._logger = logging.getLogger( 'Argument:%s' % name )
+        self._name = name
+        self._help = help
+        self._value = None
+        self._position = 0 # meaning unknown
+
+
+    def setLoggingContext(self,context):
+        name = '%s.%s' % (context, self._name)
+        self._logger = logging.getLogger( name )
+
+        
+    def help(self):
+        return self._help
+    
+
+    def fullHelp(self):
+        help = ''
+        if self.hasPosition():
+            help += '%2d ' % self.position()
+        else:
+            help += ' ? '
+        
+        help += '%s (%s,%s)' % (self._name, self.basicType(), self.argumentType())
+        if self.hasDefault(): help += ' default=%s' % (repr(self.getDefault()))
+        return '%s # %s' % (help,self.help())
+
+
+    def defaultHelp(self):
+        """Return default help string. Must be implemented in derived class"""
+        raise TransfromDefinitionError, 'defaultHelp() not implemented in class %s' % self.__class__.__name__
+    
+
+    def hasDefault(self):
+        return hasattr(self,'default')
+
+
+    def getDefault(self):
+        return getattr(self,'default',None)
+
+
+    def setDefault(self,default):
+        """ Setting the default value for this argument. Makes the argument optional."""
+        if default is None:
+            if self.hasDefault(): del self.default
+        else:
+            try: self.default = self.toPython(default)
+            except TransformDefinitionError :
+                raise TransformDefinitionError( 'Default value %s=%s is not of type %s' % \
+                                                (self._name, repr(default), self.basicType()) )
+
+
+    def setName(self,name):
+        self._name = name
+
+
+    def name(self):
+        return self._name
+
+
+    def defaultName(self):
+        """Equal to the argumentType with first character converted to lowercase"""
+        meta = self.argumentType()
+        return meta[0].lower() + meta[1:]
+
+
+    def setValue(self,value):
+        """Set the value, which can either be:
+        - a string which will be interpreted as being of type as specified by self.basicType()
+        - an objectc of the python type as specified by self.basicType()"""
+        
+        if value is None:
+            self._value = None
+        else:
+            self._value = self.toPython(value)
+
+        #return value as python type
+        return self._value
+    
+
+    def value(self):
+        return self._value
+
+
+    def hasValue(self):
+        return self._value is not None
+    
+
+    def setPosition(self,pos):
+        self._position = max(0,pos)
+            
+
+    def hasPosition(self):
+        return self._position > 0
+
+
+    def position(self):
+        return self._position
+
+
+    def argumentType(self):
+        """Type of argument, giving information about the kind of argument. Equals classname minus the 'Arg' suffix"""
+        meta = self.__class__.__name__
+        # remove the "Arg" postfix from the classname
+        meta = strip_suffix(meta, 'Arg')
+        return meta
+
+
+    def isOptional(self):
+        return hasattr(self,'default')
+
+
+    def namedTypeFormat(self):
+        return '%%(%s)%s' % (self._name,self.typeFormat())
+
+
+    def pythonVariableTemplate(self,objName=None):
+        plate = ''
+        if objName: plate += objName + '.'
+        plate += '%s = %s' % (self._name,self.namedTypeFormat())
+        return plate
+
+
+    def runArgsTemplate(self,objName):
+        return self.pythonVariableTemplate(objName)
+
+
+    def runArgsComment(self):
+        return os.linesep + CommentLine(self._help).smallComment()
+        
+
+    def jobOrTask(self):
+        return 'task'
+
+
+    def _notImplemented(self,what):
+        """Private helper function. Raise exception with error message in case something is not implemented in a class"""
+        raise TransfromDefinitionError( '%s not implemented in class %s' % (what, self.__class__.__name__) )
+
+
+    #
+    # Member functions that need to be implemented by some intermediate derived class 
+    #
+    def toPython(self,val):
+        """Should convert <val> (which is either a string or a python object of the correct type) to
+        a python object of the python type corresponding to the basicType() of this argument.
+        If <val> is not of the correct python type and can not be converted to it, a TransformArgumentError
+        exception should be raised.
+        Example types: int,float,str,list"""
+        self._notImplemented('toPython()')
+
+
+    def basicType(self):
+        """Should return a string indicating the basic python type for this argument. Mainly for debugging."""
+        self._notImplemented('basicType()')
+
+
+    def typeFormat(self):
+        """Should return a string containing the type specifier of the format string as used in the <str> % <str> syntax.
+        Example: basicType 'int' should return 'd', 'string' should return 's', etc."""
+        self._notImplemented('typeFormat()')
+
+
+    def type(self):
+        """Should return a string specifying the type as defined by the Production System"""
+        self._notImplemented('type()')
+
+
+    def metaType(self):
+        """Should return a string specifying the metaType as defined by the Production System"""
+        self._notImplemented('metaType()')
+
+
+    #
+    # Member functions to be implemented by fully specified argument classes
+    #
+    def isFullArgument(self):
+        """Should return True for a fully implemented argument that can be used in a transformation.
+        This is to avoid people using non-fully specified arguments in a transformation."""
+        return False
+
+#
+# end of class Argument
+#
+
+################################################################################
+# Add-in argument helper base classes
+################################################################################
+class ArgRange:
+    """Argument range add-in class."""
+    def __init__(self, minimum=None, maximum=None):
+        if minimum is not None: self.minimum = minimum
+        if maximum is not None: self.maximum = maximum
+
+
+    def hasMinimum(self):
+        return hasattr(self,'minimum')
+
+
+    def hasMaximum(self):
+        return hasattr(self,'maximum')
+
+
+    def getMinimum(self):
+        return getattr(self,'minimum',None)
+
+
+    def getMaximum(self):
+        return getattr(self,'maximum',None)
+
+
+    def checkRange(self,val):
+        """ Check the range of <val> (python type). Returns integer:
+         0 : in range
+        -1 : below minimum
+        +1 : above maximum
+        """
+        
+        if self.hasMinimum() and val < self.minimum: return -1
+        if self.hasMaximum() and val > self.maximum: return +1
+        return 0
+
+#
+# end of class ArgRange
+#
+
+
+
+class ArgChoices:
+    def __init__(self, choices):
+        """Argument list of choices add-in class"""
+        self.choices = choices
+
+
+    def checkChoices(self,val):
+        """ Returns boolean indicating that <val> (python type) is one of choices."""
+        return val in choices
+
+#
+# end of class ArgChoices
+#
+
+
+
+################################################################################
+# Basic type arguments
+################################################################################
+    
+class IntegerArg( Argument ):
+    """Basic argument type. Any integer. Only type is checked before running"""
+    def __init__(self,help,name):
+         Argument.__init__(self,help,name)
+
+
+    def basicType(self):
+        return 'int'
+
+         
+    def type(self):
+        return 'plain'
+
+
+    def typeFormat(self):
+        return 'd'
+
+
+    def toPython(self,val):
+        """Turn a command line argument string into an int python object"""
+        try: return int(val)
+        except ValueError :
+            raise TransformArgumentError( '%s=%s is not of type %s' % \
+                                          (self.name(), repr(val), self.basicType()) )
+        
+
+    def metaType(self):
+        return 'natural'
+
+    
+
+class FloatArg( Argument ):
+    """Basic argument type. Any float. Only type is checked before running"""
+    def __init__(self,help,name):
+        Argument.__init__(self,help,name)
+
+
+    def basicType(self):
+        return 'float'
+
+
+    def typeFormat(self):
+        return 'g'
+
+
+    def toPython(self,val):
+        """Turn a command line argument string into an float python object"""
+        try: return float(val)
+        except ValueError :
+            raise TransformArgumentError( '%s=%s is not of type %s' % \
+                                          (self.name(), repr(val), self.basicType()) )
+
+
+    def metaType(self):
+        return 'float'
+
+    
+
+class StringArg( Argument ):
+    """Basic argument type. Any string."""
+    def __init__(self,help,name):
+        Argument.__init__(self,help,name)
+
+
+    def basicType(self):
+        return 'str'
+
+
+    def type(self):
+        return 'plain'
+
+
+    def typeFormat(self):
+        return 's'
+
+
+    def toPython(self,val):
+            try: return str(val+'')
+            except TypeError :
+                raise TransformArgumentError( '%s=%s is not of type %s' % \
+                                              (self.name(), repr(val), self.basicType()) )
+
+
+    def pythonVariableTemplate(self,objName=None):
+        plate = ''
+        if objName: plate += objName + '.'
+        plate += '%s = \'%s\'' % (self.name(),self.namedTypeFormat())
+        return plate
+
+
+    def metaType(self):
+        return 'string'
+
+
+
+class FileListArg( Argument ):
+    """List of filenames coded with the following syntax:
+    <prefix>[<list>]<postfix> where '[' and ']' are litteral characters and <list>
+    gives a recipe for a list of numbers to be generated (with <n> and <m> integers): 
+    <n>,<m> (enumeration) or <n>-<m> (range including m) or a combination of those.
+    A list of filenames is generated where the [<list>] is replaced by the actual
+    integers that the list represents.
+    The [<list>] part can also be omitted, in which case a list of one filename
+    is generated (as given)."""
+
+    def __init__(self,help,name):
+        Argument.__init__(self,help,name)
+
+
+    def basicType():
+        return 'list'
+
+
+    def type(self):
+        return 'plain'
+
+
+    def toPython(self,valIn):
+        # if already a list, nothing to be done
+        if type(valIn) == type(list()): return valIn
+        # make a list of python types out of the strings
+        if type(valIn) == type(''):
+            all = StringNumberList(valIn).getStringList()
+            if all is None:
+                raise TransformArgumentError( '%s=%s: Invalid string range syntax' % \
+                                              (self.name(),valIn) )
+            return all
+        
+        # if we get here, there is problem
+        raise TransformDefinitionError( '%s=%s: value is not a list or a string' % (self.name(), valIn) )
+
+
+
+class StringChoicesArg( ArgChoices, StringArg ):
+    """A string from a possible list of strings. Tested before running"""
+    def __init__(self,help,name,choices):
+        ArgChoices.__init__(self, choices)
+        StringArg.__init__(self,help,name)
+
+
+    def type(self):
+        return 'plain'
+
+
+    def preRunAction(self):
+        """Return boolean indicating if <argVal> is one of choices"""
+        val = self.value()
+        name = self.name()
+        choices = self.choices
+        if not ArgChoices.checkChoices(self,val):
+            raise TransformArgumentError( '%s=%s is not in %s' %
+                                          (name, repr(val), choices) )
+
+        self._logger.debug( '%s is in list %s -> OK' % (repr(val), repr(choices)) )
+
+            
+#
+# end of class StringChoicesArg
+#
+
+
+class InputFileArg( Filename, StringArg ):
+    """Input file. Name suffix, existence and readability of file is checked before run"""
+    def __init__(self,help,name,type,contents):
+        Filename.__init__(self,type,contents)
+        StringArg.__init__(self,help,name)
+        
+        
+    def preRunAction(self):
+        """Check that the file has the correct suffix, exists, and is readable"""
+        val = self.value()
+        if not self.checkSuffix( val ):
+            raise TransformArgumentError( '%s=%s does not have suffix %s' % (self.name(),repr(val),self.fileSuffix()) )
+        
+        if not Filename.exists(val):
+            att = Filename.existsAttempt(val);
+            if att:
+                self._logger.warning('replacing %s with %s' % (val,att) )
+                self.setValue(att)
+                val = att
+            else:
+                raise InputFileError( '%s not found' % val )
+
+        if not Filename.isReadable(val):
+            raise InputFileError( '%s exists but is not readable' % val )
+
+
+        self._logger.debug( 'Inputfile %s suffix OK and file is found -> OK' % (self.name(), self.fileSuffix(), val) )
+    
+
+    def type(self):
+        return 'inputDS'
+
+
+    def metaType(self):
+        return self.fileContents().upper()
+
+    
+
+class InputFileListArg( Filename, FileListArg ):
+    """List of input files. Name suffix, existence and readability of all files is checked before run"""
+    def __init__(self,help,name,type,contents):
+        Filename.__init__(self,type,contents)
+        FileListArg.__init__(self,help,name)
+        
+        
+    def preRunAction(self):
+        """Check that the file has the correct suffix, exists, and is readable"""
+        vals = self.value()
+        for val in vals:
+            if not self.checkFileSuffix( val ):
+                raise TransformArgumentError( '%s=%s does not have suffix %s' % (self.name(),repr(val),self.fileSuffix()) )
+        
+            if not Filename.exists(val):
+                att = Filename.existsAttempt(val);
+                if att:
+                    self._logger.warning('replacing %s with %s' % (val,att) )
+                    vals[vals.index(val)] = att
+                    val = att
+                else:
+                    raise InputFileError( '%s not found' % val )
+
+            if not Filename.isReadable(val):
+                raise InputFileError( '%s exists but is not readable' % val )
+
+
+        self._logger.debug( 'Inputfiles %s suffices OK and all files are found -> OK' % (self.name(), self.fileSuffix(), val) )
+    
+
+    def type(self):
+        return 'inputDSlist'
+
+
+    def metaType(self):
+        return self.fileContents().upper()
+    
+
+class InputDataFileArg( InputFileArg ):
+    """Class for input POOL data files"""
+    def __init__(self,help,name,contents,type=PoolDataFile.defaultType):
+        InputFileArg.__init__(self,help,name,type,contents)
+    
+
+
+class InputTarFileArg( InputFileArg ):
+    def __init__(self,help,name,destdir='.',type='tar|tar.gz|tgz'):
+        InputFileArg.__init__(self,help,name,type,None)
+        self._filelist = [ ]
+        self._destdir = destdir
+
+
+    def filelist(self):
+        """Return a list of filenames in archive"""
+        if not self._filelist:
+            if not self.hasValue():
+                raise TransformDefinitionError( "%s filelist() called before value is set" % self.name() )
+            tar = tarfile.open(self.value())
+            self._filelist = tar.getnames()
+            tar.close()
+        return self._filelist
+
+
+    def filelistRE(self,pattern):
+        """Return list of files in archive that match the regular expression <pattern>"""
+        files = self.filelist()
+        return [ s for s in files if re.search( pattern, s ) ]
+
+
+    def extract(self):
+        """Extract all files from the archive and check their presence"""
+        # check that we have a value
+        if not self.hasValue():
+            raise TransformDefinitionError( "%s extract() called before value is set" % self.name() )
+        # do the extraction
+        tar = tarfile.open(self.value())
+        for f in tar.getmembers(): tar.extract(f, self._destdir)
+        # bonus: set the filelist
+        self._filelist = tar.getnames()
+        tar.close()
+        # check that all files are extracted
+        for f in self.filelist():
+            if not Filename.exists( os.path.join( self._destdir, f ) ):
+                raise TransformArgumentError( "%s=%s FAILED to extract file %s from archive",
+                                              self.name(), self.value(), f )
+
+
+    def preRunAction(self):
+        """Check presence of file and untar/zip it"""
+        InputFileArg.preRunAction(self)
+        self.extract()
+
+       
+
+
+class OutputFileArg( Filename, StringArg ):
+    """Output file. Name suffix is tested before run, and file is deleted before run (if needed).
+    Existence of file is tested after run"""
+    def __init__(self,help,name,type,contents):
+        Filename.__init__(self,type,contents)
+        StringArg.__init__(self,help,name)
+
+
+    def preRunAction(self):
+        """Remove output file before running"""
+        val = self.value()
+        mess = ''
+        if val.upper() == 'NONE':
+            mess = 'No output file expected. Nothing to be done.'
+        elif not self.checkFileSuffix(val):
+            raise TransformArgumentError( '%s=%s does not have suffix %s' % (self.name(), repr(val), self.fileSuffix()) )
+        elif Filename.exists(val):
+            try: os.remove(val)
+            except:
+                raise OutputFileError( 'ERROR while trying to pre-remove expected output file %s=%s' % (self.name(),repr(val)) )
+            else:
+                mess = 'Checked suffix %s and removed output file %s -> OK' % (self.fileSuffix(), val)
+        else:
+            mess = 'Checked suffix %s. No need to remove output file %s (not present)' % (self.fileSuffix(), val)
+
+        self._logger.debug( mess )
+
+
+    def postRunAction(self):
+        """Check that output file exists."""
+        val = self.value()
+        mess = ''
+        if val.upper() == 'NONE':
+            mess = 'No output file expected. Nothing to be done.'
+        elif not Filename.exists(val):
+            raise OutputFileError( '%s=%s not found' % (self.name(),repr(val)) )
+        elif not Filename.isRegular(val):
+            raise OutputFileError( '%s=%s is not a regular file' % (self.name(),repr(val)) )
+        else:
+            mess = 'Outputfile %s found. -> OK' % (self.value())
+
+        self._logger.debug( mess )
+    
+
+    def type(self):
+        return 'outputDS'
+
+
+    def metaType(self):
+        return self.fileContents().upper()
+
+
+

python/full_trfarg.py

+#######################################################################################
+# End-user Fully specialised arguments that can be used in JobTransform implemenations.
+#######################################################################################
+
+import PyJobTransformsCore.basic_trfarg as trfarg
+from   PyJobTransformsCore.trfutil import *
+
+class InputEvgenFileArg(EvgenFile,trfarg.InputFileArg):
+    """Input file that contains generated events"""
+    def __init__(self,help='output data file with generated events',name='default',
+                 contents=EvgenFile.defaultContents,type=EvgenFile.defaultType):
+        trfarg.InputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+
+class OutputEvgenFileArg(EvgenFile,trfarg.OutputFileArg):
+    """Output file that contains generated events"""
+    def __init__(self,help='output data file with generated events',name='default',
+                 contents=EvgenFile.defaultContents,type=EvgenFile.defaultType):
+        trfarg.OutputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+
+
+class InputHitsFileArg(HitsFile,trfarg.InputFileArg):
+    """Input file that contains generated events"""
+    def __init__(self,help='output data file with generated events',name='default',
+                 contents=HitsFile.defaultContents,type=HitsFile.defaultType):
+        trfarg.InputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+
+class OutputHitsFileArg(HitsFile,trfarg.OutputFileArg):
+    """Output file that contains generated events"""
+    def __init__(self,help='output data file with generated events',name='default',
+                 contents=HitsFile.defaultContents,type=HitsFile.defaultType):
+        trfarg.OutputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+
+
+
+class OutputHistogramFileArg(HistogramFile,trfarg.OutputFileArg):
+    """Output file that contains histograms."""
+    def __init__(self,help='output histogram file',name='histogramFile',
+                 contents=HistogramFile.defaultContents,type=HistogramFile.defaultType):
+        trfarg.OutputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+
+
+class OutputNtupleFileArg(NtupleFile,trfarg.OutputFileArg):
+    """Output file that contains ntuples."""
+    def __init__(self,help='output ntuple file',name='ntupleFile',
+                 contents=NtupleFile.defaultContents,type=NtupleFile.defaultType):
+        trfarg.OutputFileArg.__init__(self,help,name,type,contents)
+
+    def isFullArgument(self):
+        return True
+
+    
+
+class JobOptionsArg(trfarg.StringArg):
+    def __init__(self,help='jobOptions file to use',package=None,name='default'):
+        if package is not None:
+            self.package = package
+            help += '. Default package: %s' % (package)
+        trfarg.StringArg.__init__(self,help,name)
+
+
+    def isFullArgument(self):
+        return True
+
+
+    def hasPackage(self):
+        return hasattr(self,'package')
+
+
+    def getPackage(self):
+        return getattr(self,'package',None)
+
+
+    def value(self):
+        val = trfarg.Argument.value(self)
+        if val and hasattr(self,'package') and not os.sep in val:
+            val = os.path.join(self.package, val)
+
+        return val
+        
+
+
+    def preRunAction(self):
+        """Check than jobOptions file can be found"""
+        val = self.value()
+        full = find_joboptions( val )
+        if not full:
+            raise TransformArgumentError( '%s=%s not found' % (self.name(),repr(val)) )
+
+        self._logger.debug( 'Found %s in %s' % (val, strip_suffix(full,val)) )
+
+
+#
+# end of class JobOptionsArg
+#
+
+class RandomSeedArg(trfarg.IntegerArg):
+    def __init__(self,help,name='default'):
+        trfarg.IntegerArg.__init__(self,help,name)
+
+    def isFullArgument(self):
+        return True
+
+    def jobOrTask(self):
+        return 'job'
+
+
+
+class RunNumberArg(trfarg.IntegerArg):
+    def __init__(self,help='run number of data file',name='default'):
+        trfarg.IntegerArg.__init__(self,help,name)
+        
+    def isFullArgument(self):
+        return True
+
+
+
+
+class FirstEventArg(trfarg.IntegerArg):
+    def __init__(self,help='first event number to use',name='default'):
+        trfarg.IntegerArg.__init__(self,help,name)
+
+    def isFullArgument(self):
+        return True
+
+    def jobOrTask(self):
+        return 'job'
+
+
+
+class SkipEventsArg(trfarg.IntegerArg):
+    def __init__(self,help='number of events to skip',name='default'):
+        trfarg.IntegerArg.__init__(self,help,name)
+
+    def isFullArgument(self):
+        return True
+
+    def jobOrTask(self):
+        return 'job'
+
+
+        
+
+class MaxEventsArg(trfarg.IntegerArg):
+    def __init__(self,help='maximum number of events to process',name='default'):
+        trfarg.IntegerArg.__init__(self,help,name)
+
+    def isFullArgument(self):
+        return True
+
+
+    

python/runargs.py

+import os
+
+class RunArguments:
+    """Dynamic class that holds the run arguments as named members with values."""
+    def __str__(self):
+        myself = 'RunArguments:'
+        for arg in dir(self):
+            if not arg.startswith('__'):
+                myself += '%s   %s = %s' % (os.linesep, arg, repr(getattr(self,arg)))
+
+        return myself

python/skeleton.py

+# old code for auto-skeleton generation
+
+
+class ServiceUtil:
+    def __init__(self,name,member,Dlls):
+        self.name = name
+        self.member = member
+        # turn single string into a list
+        if type(Dlls) == type(''): Dlls = [ Dlls ]
+        self.Dlls = Dlls
+
+
+    def getDlls(self):
+        return self.Dlls
+
+    def getName(self):
+        return self.name
+
+
+    def getMemberName(self):
+        return self.member
+
+
+    def getMemberFullName(self):
+        return '%s.%s' % (self.name,self.member)
+
+
+    def getDllsTemplate(self):
+        Dlls = self.Dlls
+        if not Dlls: return ''
+        if len(Dlls) == 1:
+            Dllstr = repr(Dlls[0])
+            return "if %s not in theApp.Dlls: theApp.Dlls += [ %s ]" % (Dllstr,Dllstr)
+        else:
+            return "theApp.Dlls += [ s for s in %s if s not in theApp.Dlls ]" % \
+                   ( repr(self.Dlls) )
+
+
+    def getServiceTemplate(self):
+        name = self.name
+        return "%s = Service( %s )" % (name, repr(name))
+
+
+    def ifPropertyExistsTemplate(self):
+        return "if \'%s\' in %s.properties():" % (self.getMemberName(),self.getName())
+
+
+    def removeItemsFromListReTemplate(self,regexp):
+        name = self.getMemberFullName()
+        return "%s = [ s for s in %s if not re.search('%s',s) ]" % \
+               ( name, name, regexp )
+                   
+
+    def addItemToListTemplate(self,item):
+        return "%s += [ %s ]" % (self.getMemberFullName(),item)
+
+        
+    def addItemsToListTemplate(self,itemList):
+        name = self.getMemberFullName()
+        itemsep = ',' + os.linesep + (len(name)+6)*' '
+        return "%s += [ %s ]" % (name, itemsep.join(itemList))
+
+
+    def setMemberTemplate(self,value):
+        return "%s = %s" % (self.getMemberFullName(),value)
+
+
+
+
+class RandomStream:
+    def __init__(self, name, *seeds):
+        """ Arguments can be given in a variety of ways:
+        1. <name> can contain the full string, as normally used in AtRdnmGenSvc.Seeds,
+        2. <name> can contain only the stream name (will become first word in the full string).
+           <seeds> is a variable list of seed arguments given as integer,string or Argument type.
+           A special seed called 'self' will be replaced by the value of the RandomSeedArg
+        to which this RandomStream is given an argument in its constructor.
+        """
+        # name can contain stream name and seeds
+        s = name.split()
+        self.name = s[0]
+        if len(s) > 1:
+            self.seeds = s[1:]
+        else:
+            self.seeds = [ ]
+
+        for s in seeds:
+            if type(s) == type(''):
+                self.seeds += s.split()
+            else:
+                self.seeds.append(s)
+
+
+    def getName(self):
+        return self.name
+
+
+    def getSeeds(self):
+        return self.seeds
+
+
+    def getItem(self):
+        form = self.name
+        args = [ ]
+        for seed in self.seeds:
+            if hasattr(seed,'typeFormat') and hasattr(seed,'getName') :
+                form += ' %%' + seed.typeFormat()
+                args.append( seed.getName() )
+            else:
+                form += ' ' + str(seed)
+
+        item = repr(form)
+        if args : item += ' %% (' + ','.join(args) + ')' 
+        return item
+
+
+
+class JobOptionsArg(StringArg, PreRunCommand):
+    def __init__(self,help,package=None,name='default'):
+        if package is not None:
+            self.package = package
+            help += '. Default package: %s' % (package)
+        StringArg.__init__(self,help,name)
+
+
+    def hasPackage(self):
+        return hasattr(self,'package')
+
+
+    def getPackage(self):
+        return getattr(self,'package',None)
+
+
+    def value(self):
+        val = Argument.value(self)
+        if val and hasattr(self,'package') and not os.sep in val:
+            val = os.path.join(self.package, val)
+
+        return val
+        
+
+
+    def preRunAction(self):
+        """Check than jobOptions file can be found"""
+        val = self.value()
+        full = find_joboptions( val )
+        if not full:
+            raise TransformArgumentError( '%s=%s not found' % (self.name(),repr(val)) )
+
+        self._logger.debug( 'Found %s in %s' % (val, strip_suffix(full,val)) )
+
+
+    def jobOptionsTemplate(self):
+        lines = [ ]
+        if self.hasPackage():
+            lines += [ "# add default package path if no package in filename" ]
+            lines += [ "if %s.rfind(os.sep) == -1: %s = os.path.join(%s,%s)" % \
+            ( self.name(), self.name(), repr(self.getPackage()), self.name() ) ]
+        lines += [ 'include( %s )' % (self.name()) ]
+        return os.linesep.join(lines)
+
+#
+# end of class JobOptionsArg
+#
+
+class RandomSeedArg(IntegerArg):
+    def __init__(self,streams,help='default',name='default'):
+        self.streams = [ ]
+        self.addStreams( streams )
+        self.service = ServiceUtil('AtRndmGenSvc','Seeds','AthenaServices')
+        # init base class late because it calls defaultHelp(), which needs the streams set 
+        IntegerArg.__init__(self,help,name)
+
+
+
+    def defaultHelp(self):
+        return 'random seed for streams ' + ','.join( [ s.name() for s in self.streams ] ) 
+
+
+    def getProdLevel(self):
+        return 'job'
+
+
+    def addStreams(self,streams):
+        # make sure I have a list
+        if type(streams) != type(list()): streams = [ streams ] 
+        for s in streams:
+            if type(s) == type(''):
+                stream = RandomStream(s)
+            else:
+                stream = s
+                
+            newseeds = [ ]
+            for seed in stream.getSeeds():
+                if str(seed) == 'self' : seed = self
+                newseeds.append( seed )
+            self.streams.append( RandomStream( stream.name(), *newseeds ) )
+
+
+    def getStreamItems(self):
+        return [ s.getItem() for s in self.streams ]
+        
+
+    def postOptionsTemplate(self):
+        svc = self.service
+        indent = '    '
+        lines = [ "import re" ]
+        lines += [ svc.getDllsTemplate() ]
+        lines += [ svc.getServiceTemplate() ]
+        streams = self.streams
+        if streams:
+            namelist = [ s.name() for s in streams ]
+            # for testing
+ #           lines += [ svc.setMemberTemplate( repr([ s+' to be removed' for s in namelist ] + [ "TESTSTREAM to stay" ]) ) ]
+            # first remove all old streams if needed
+            lines += [ "#remove old streams if needed (using regular expression)" ]
+            lines += [ svc.ifPropertyExistsTemplate() ]
+            regexp = "^\s*(%s)(\s+|$)" % '|'.join( namelist )
+            lines += [ indent + svc.removeItemsFromListReTemplate( regexp ) ]
+            lines += [ "else:" ]
+            lines += [ indent + svc.setMemberTemplate( repr([ ]) ) ]
+            # now add new streams
+            lines += [ "#adding new streams" ]
+            lines += [ svc.addItemsToListTemplate( self.getStreamItems() ) ]
+
+        return os.linesep.join(lines)
+        
+
+
+class RunNumberArg(IntegerArg):
+    def __init__(self,help,name='default'):
+        IntegerArg.__init__(self,help,name)
+        self.service = ServiceUtil('EventSelector', 'RunNumber', 'AthenaServices')
+
+    def postOptionsTemplate(self):
+        svc = self.service
+        lines = [ svc.getDllsTemplate(), svc.getServiceTemplate(), svc.setMemberTemplate( self.name() ) ]
+        return os.linesep.join(lines)
+        
+        
+
+
+class FirstEventArg(IntegerArg):
+    def __init__(self,help='first event number to use',name='default'):
+        IntegerArg.__init__(self,help,name)
+        self.service = ServiceUtil('EventSelector', 'FirstEvent', 'AthenaServices')
+
+
+    def postOptionsTemplate(self):
+        svc = self.service
+        lines = [ svc.getDllsTemplate(), svc.getServiceTemplate(), svc.setMemberTemplate( self.name() ) ]
+        return os.linesep.join(lines)
+        
+
+    def getProdLevel(self):
+        return 'job'
+
+
+
+class MaxEventsArg(IntegerArg):
+    def __init__(self,help='maximum number of events to process',name='default'):
+        IntegerArg.__init__(self,help,name)
+
+
+    def postOptionsTemplate(self):
+        return "theApp.EvtMax = %s" % self.name()
+
+    
+
+import os,sys,time,exceptions,getopt,stat
+from math import *
+
+from AthenaCommon.Logging import logging
+
+from PyJobTransformsCore import trfenv
+from PyJobTransformsCore import trferr
+from PyJobTransformsCore.trferr import *
+from PyJobTransformsCore.trfutil import *
+from PyJobTransformsCore.runargs import * 
+from PyJobTransformsCore.basic_trfarg import Argument
+
+
+
+
+class JobTransform:
+    # some static constants
+    _fileSuffix = '_trf'
+    _fileExtension = '.py'
+    _userOptsShort = "hd"
+    _userOptsLong  = [ 'help', 'debug' ]
+    _userOptsArg   = [ ''    , ''      ]
+    _userOptsHelp  = [
+        'Print detailed help',
+        'Print debug messages' ]
+    _runArgsName = 'runArgs'
+    
+    def __init__( self, version, authors, help, skeleton='default', name='default' ):
+        # set some dynamic defaults to arguments
+        filename = os.path.basename(sys._getframe(1).f_code.co_filename)
+        if name == 'default':
+            # start from filename where constructor is called
+            name = filename
+            # remove extension
+            name = strip_suffix( name, JobTransform._fileExtension )
+            # remove suffix
+            name = strip_suffix( name, JobTransform._fileSuffix )
+        if skeleton == 'default':
+            skeleton = "skeleton.%s.py" % name
+        #  set logger
+        self._loggerName = name
+        self._logger = logging.getLogger( self._loggerName )
+        self._logger.setLevel( logging.INFO )
+        trferr.errorHandler.setLoggingContext( self._loggerName )
+        # arguments
+        self._filename = filename
+        self._name    = name
+        self._version = version
+        self._help    = help
+        self._jobOpts = [ ]
+        self._authors = [ ]
+        self._namedArgs = { }
+        self._positionalArgs = [ ]
+        self._optionalArgs = { }
+        self._requiredArgs = { }
+        self._skeleton = skeleton
+        self._runArgs = RunArguments()
+        # prerun and postrun actions
+        self._preRunActions = [ ]
+        self._postRunActions = [ ]
+        # store the various jobOption templates
+        self._authorsTemplate = ''
+        self._runArgsHeaderTemplate = ''
+        self._runArgsCodeTemplate = ''
+        self._runArgsArgumentsTemplate = ''
+        self._runArgsTemplate = ''
+        self._skeletonHeaderTemplate = ''
+        self._skeletonCodeTemplate = ''
+        self._skeletonTemplate = ''
+
+        self.addTemplates( 'runArgsHeader',
+                           "# Run arguments file auto-generated on %s by:" % (time.ctime()) ,
+                           "# JobTransform: %s" % (name) ,
+                           "# Version: %s" % (version) )
+                          
+                           
+        # initialise the skeletonHeader template
+        self.addTemplates( 'skeletonHeader',
+                           "# JobOptions skeleton file auto-generated on %s by:" % (time.ctime()) ,
+                           "# JobTransform: %s" % (name) ,
+                           "# Version: %s" % (version) )
+                           
+        self.addAuthors( authors )
+
+        self.addTemplates( 'skeletonCode', "import os,sys" )
+        
+        self.addTemplates( 'runArgsCode',
+                           "from PyJobTransformsCore.runargs import RunArguments",
+                           "",
+                           "%s = RunArguments()" % self.runArgsName() )
+                           
+
+
+    def defaultSkeletonFilename(self):
+        return 'skeleton.' + self._name + '.py'
+
+
+    def skeletonFilename(self):
+        return self._skeleton
+
+
+    def runArgsFilename(self):
+        return 'runargs.' + self._name + '.py'
+
+
+    def runArgsName(self):
+        """Return a string containing the name of the object that holds the run arguments"""
+        return JobTransform._runArgsName
+    
+
+    def runArgs(self):
+        return self._runArgs
+
+
+    def version(self):
+        return self._version
+    
+
+    def updateFullTemplates(self):
+        # invalide full templates, so they get recreated when needed
+        self._skeletonTemplate = ''
+        self._runArgsTemplate = ''
+        
+
+    def addTemplates(self, name, *vargs):
+        # count the number of non-None arguments
+        name = '_%sTemplate' % (name)
+        val = getattr(self, name)
+        for arg in vargs: val += str(arg) + os.linesep
+        setattr(self, name, val)
+        self.updateFullTemplates()
+
+
+    def addAuthors(self, authorList):
+        if not authorList: return
+        # make sure I got a list
+        authorsAdd = authorList
+        if isinstance(authorList,Author):
+            authorsAdd = [ authorList ]
+        else:
+            authType = type(authorList)
+            if authType == type(''):
+                authorsAdd = [ Author(a) for a in authorList.split(',') ]
+            elif authType == type(list()):
+                pass
+            else:
+                raise TransformDefinitionError('Author type %s not supported' % authType)
+
+        self._authors += authorsAdd
+        # update authors template
+        authStrings = [ str(a) for a in authorsAdd ]
+        firstLine  = "# Authors: "
+        otherLines = "#" + ' '*(len(firstLine)-1)
+        sep = os.linesep + otherLines
+        i = 0
+        if not self._authorsTemplate:
+            self.addTemplates( 'authors', "%s%s" % (firstLine,authStrings[i]) )
+            i += 1
+        if i < len(authorsAdd):
+            self.addTemplates( 'authors', "%s%s" % (otherLines,sep.join(authStrings[i:])) )
+
+
+
+
+    def _addArgument(self,arg, default=None):
+        """Private function. Not to be called from outside. Call 'add()' instead.
+        Add an command line argument definition.
+        arg     : instance of an Argument class (derived from class Argument)
+
+        The added Arguments are given positional numbers (for command line invocation)
+        in the order in which they are added. If an Argument is optional (i.e. has a
+        default value), then all Arguments added after that must be optional as well."""
+
+        # ensure usage of fully specified arguments
+        if not arg.isFullArgument():
+            raise TransformDefinitionError( 'Class %s is not a fully specified Argument class.' % arg.__class__.__name__ )
+
+
+        name = arg.name()
+        key = name.lower()
+        #check that I have not two arguments with the same name
+        na = self._namedArgs
+        if key in na:
+            # make name unique by adding a number (starting at 2)
+            i = 2
+            newkey = key + str(i)
+            while newkey in na:
+                i += 1
+                if ( i >= 100 ): # 100 is just an arbitrary large number to protect against crazy input
+                    raise TransformArgumentError, 'Could not make unique name for multiple argument %s' % (name)
+                newkey = key + str(i)
+            #also update name
+            arg.setName(arg.name() + str(i))
+
+
+        # set context for logging messages
+        arg.setLoggingContext( self._loggerName )
+        # add to named and positional lists
+        self._namedArgs[key] = arg
+        self._positionalArgs.append( arg )
+        arg.setPosition( len(self._positionalArgs) )
+        # handle default
+        if default:
+            arg.setDefault(default)
+            self.setArgument(key,default)
+            # argument is optional
+            self._optionalArgs[key] = arg
+        else:
+            # argument in not optional
+            # check that all optional arguments are at the end
+            if len(self._optionalArgs) > 0:
+                raise TransformDefinitionError, 'Non-optional argument (%s) after optional argument(s)' % (name)
+            self._requiredArgs[key] = arg
+
+
+        self.addTemplates( 'runArgsArguments',   arg.runArgsComment(),  arg.runArgsTemplate(self.runArgsName()) )
+
+
+    def _addPreRunAction(self, cmd):
+        self._preRunActions.append( cmd )
+
+
+    def _addPostRunAction(self, cmd):
+        self._postRunActions.append( cmd )
+
+
+    def setArgument(self,argName,argValue):
+        """set value of an already defined argument"""
+        key=argName.lower()
+        args = self._namedArgs
+        if key in args.keys():
+            val = args[key].setValue( argValue ) # get the python type of the value
+            setattr(self._runArgs, argName, val)
+        else:
+            raise TransformArgumentError( '%s does not have an argument named %s' % (self.name(), argName) )
+        
+
+    def ls(self):
+        """List the arguments of this transformation"""
+        for arg in self._positionalArgs:
+            print '%s=%s # %s' % (arg.name(), arg.value(), arg.getHelp())
+
+
+    def name(self):
+        return self._name
+
+
+    def filename(self):
+        return self._filename
+
+
+    def add(self, *vargs):
+        arg1 = vargs[0]
+        if isinstance(arg1,Argument): self._addArgument(*vargs)
+        if isinstance(arg1,Author): self.addAuthor(*vargs)
+        if hasattr(arg1,'preRunAction'): self._addPreRunAction(arg1)
+        if hasattr(arg1,'postRunAction'): self._addPostRunAction(arg1)
+
+
+
+    def getRunArgsTemplate(self):
+        if not self._runArgsTemplate:
+            lines = [ self._runArgsHeaderTemplate,
+                      self._authorsTemplate,
+                      CommentLine.hashLine(),
+                      self._runArgsCodeTemplate,
+                      self._runArgsArgumentsTemplate ]
+            self._runArgsTemplate = os.linesep.join( lines ) 
+
+        return self._runArgsTemplate
+
+
+    def getSkeletonTemplate(self):
+        if not self._skeletonTemplate:
+            lines = [ self._skeletonHeaderTemplate,
+                      self._authorsTemplate,
+                      CommentLine.hashLine(),
+                      self._skeletonCodeTemplate ]
+            
+            self._skeletonTemplate = os.linesep.join( lines ) 
+
+        return self.skeletonTemplate
+
+
+    def argumentList(self):
+        """Return a list of the arguments, in order of position on the command line"""
+        return self._positionalArgs
+
+
+    def argumentValueDict(self):
+        """Return a dictionary with the argument values, with their names as keys""" 
+        d = { }
+        for arg in self._positionalArgs: d.update( { arg.name(): arg.value() } )
+        return d
+
+
+    def writeRunArgs(self):
+        filename = self.runArgsFilename()
+        f = file(filename ,'w')
+        self._logger.info( 'Writing runArgs to file \"%s\"' % filename )
+        f.write( self.getRunArgsTemplate() % self.argumentValueDict() )
+        f.close()
+
+
+    def writeSkeleton(self):
+        raise TransformDefinitionError( '%s %s %s' %
+                                        ('Auto-generation of skeleton jobOptions file not yet supported.',
+                                         'It must be specified in the constructor of',
+                                         self.__class__.__name__ ) )
+        filename = self.skeletonFilename()
+        f = file(filename,'w')
+        self._logger.info( 'Writing skeleton to file \"%s\"' % filename )
+        f.write( self.getSkeletonTemplate() % self.argumentValueDict() )
+        f.close()
+
+
+    def getHelp(self):
+        return self._help
+
+
+    def getUsage(self):
+        cmd = self.filename()
+        use = 'usage: %s [options]' % cmd
+        for arg in self._positionalArgs:
+            if arg.isOptional():
+                use += ' [%s]' % arg.name().lower()
+            else:
+                use += ' <%s>' % arg.name().lower()
+        use += os.linesep
+        use += '       %s -h: get detailed help' % cmd
+        return use
+
+
+    def getFullOptionsHelp(self):
+        """return formatted multi-line string containing the help for the options"""
+        help = [ 'Options:' ]
+        indent = '   '
+        optsShort = JobTransform._userOptsShort
+        optsLong  = JobTransform._userOptsLong
+        optsArg   = JobTransform._userOptsArg
+        optsHelp  = JobTransform._userOptsHelp
+        nOpts  = len(optsShort.replace(':',''))
+        nShort = len(optsShort)
+        nLong  = len(optsLong)
+        nArg   = len(optsArg)
+        nHelp = len(optsHelp)
+        iShort = 0
+        maxOpt = 0
+        optsList = [ ]
+        # make options list and determine max string length
+        for iOpt in range(nOpts):
+            opt = '-%s' % optsShort[iShort]
+            if iOpt < nLong:
+                opt += ',--%s' % strip_suffix(optsLong[iOpt],'=')
+            hasArg = (iShort+1 < nShort) and (optsShort[iShort+1] == ':')
+            if (hasArg):
+                iShort += 1 #shift past the ':'
+                # get default argument name
+                arg = ''
+                if iOpt < nArg: arg = optsArg[iOpt]
+                if not arg: arg = 'arg' 
+                opt += ' <%s>' % arg
+            optsList.append( opt )
+            maxOpt = max(maxOpt,len(opt))
+            iShort += 1 # move to next short option
+
+        helpColumn = len(indent) + maxOpt + 2
+        helpIndent = ' '*(helpColumn+2) # indent for help continuation lines
+        for iOpt in range(nOpts):
+            opt = optsList[iOpt]
+            helpSpace = ' '*(helpColumn - len(opt) - len(indent))
+            h = ''
+            if iOpt < nHelp: h = optsHelp[iOpt]
+            hLines = h.split(os.linesep)
+            # add first line
+            help += [ '%s%s%s%s' % (indent, opt, helpSpace, hLines[0]) ]
+            # add other lines (if present)
+            if len(hLines) > 1:
+                help += [ '%s%s' % (helpIndent,s) for s in hLines[1:] ]
+            
+        return os.linesep.join(help)
+
+
+    def getFullArgsHelp(self):
+        help = [ 'Arguments:' ]
+        help += [ '  ' + arg.fullHelp() for arg in self._positionalArgs ]
+        return os.linesep.join( help )
+
+        
+    def getFullHelp(self):
+        use = [ 'JobTransform %s version %s'  % (self.name(),self.version()) ,
+                self.getHelp(),
+                self.getUsage(),
+                self.getFullOptionsHelp(),
+                self.getFullArgsHelp() ]
+        return os.linesep.join(use)
+
+
+
+    def usage(self):
+        """Print short usage message on screen"""
+        print self.getUsage()
+
+
+    def help(self):
+        """Print full help on screen"""
+        print self.getFullHelp()
+        
+
+    def ensureSkeleton(self):
+        """Make sure that the skeleton jobOptions file can be found and/or auto-generated.
+           In case of any problem, an exception is raised. If the function returns, all is OK."""
+        fn = self._skeleton
+        if fn is None:
+            # auto-generate skeleton jobOptions file
+            self._skeleton = self.defaultSkeletonFilename()
+            self.writeSkeleton()
+            fn = self._skeleton
+        # get joboptions search path
+        # try to find the file in the search path
+        if not find_joboptions( fn ):
+            raise TransformEnvError( 'Skeleton file %s not found in %s' % (fn,trfenv.JOBOPTIONSPATH) )
+
+
+
+    def processOpts(self,argList):
+        """Process the options in the full argument list. Return the list of non-option arguments."""
+        #process options
+        try:
+            opts,args = getopt.getopt(argList, JobTransform._userOptsShort, JobTransform._userOptsLong)
+        except getopt.GetoptError:
+            # find out the culprit
+            wrongOpts = [ ]
+            mess = "Error in options"
+            for i in range(len(argList)):
+                arg = argList[i]
+                if arg.startswith('--'):
+                    # long argument
+                    if len(arg) > 2:
+                        if arg[2:] not in JobTransform._userOptsLong:
+                            if arg[2:]+'=' in JobTransform._userOptsLong:
+                                mess = 'Option %s requires an argument' % arg
+                                break
+                            else:
+                                mess = 'Option %s not supported' % arg
+                                break
+                    else:
+                        mess = 'Option %s not supported' % arg
+                        break
+                elif arg.startswith('-'):
+                    #short argument
+                    if len(arg) > 1:
+                        i = JobTransform._userOptsShort.find(arg[1])
+                        if i == -1:
+                            mess = 'Option %s not supported' % arg
+                            break
+                        elif JobTransform._userOptsShort[i+1] == ':':
+                            mess = 'Option %s requires an argument' % arg
+                            break
+                    else:
+                        mess = 'Option %s not supported' % arg
+                        break
+            
+            raise TransformArgumentError( mess )
+
+            
+        for opt, arg in opts:
+            if opt in ('-h', '--help'):
+                self.help()
+                trferr.exit()
+            elif opt in ('-d','--debug'):
+                self._logger.setLevel( logging.DEBUG )
+            else:
+                assert False, 'BUG in %s: Option %s not yet implemented' % (os.path.basename(__file__),opt)
+        
+
+        return args
+
+
+    def processArgs(self, argList):
+        """Process list of arguments including the options"""
+        #Print usage when no arguments are given
+        if len(argList) == 0:
+            self.usage()
+            trferr.exit()
+        # process options
+        args = self.processOpts(argList)
+        # check the number of arguments
+        nArgs = len(args)
+        if nArgs > len(self._positionalArgs):
+            raise TransformArgumentError, 'too many arguments: %d (max=%d)' % \
+                  (nArgs, len(self._positionalArgs))
+
+        if nArgs < len(self._requiredArgs):
+            raise  TransformArgumentError, 'too few arguments: %d (min=%d)' % \
+                  (nArgs, len(self._requiredArgs))
+
+        # fill the dictionary with all given arguments
+        for i in range(nArgs):
+            self.setArgument( self._positionalArgs[i].name(), args[i] )
+
+        # fill missing ones with default values
+        for arg in self._positionalArgs:
+            if not arg.hasValue() and arg.hasDefault(): arg.setValue( arg.getDefault() )
+
+
+    def writeLastCommand(self):
+        lastname = 'last.' + self._name
+        lastfile = open(lastname,'w')
+        lastfile.write( ' '.join(sys.argv) + os.linesep)
+        lastfile.close()
+        os.chmod(lastname, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH )
+
+
+    def exePreRunActions(self):
+        self._logger.debug("Going to execute Pre-Run commands...")
+        for cmd in self._preRunActions: cmd.preRunAction()
+        self._logger.debug("Done executing Pre-Run commands")
+
+
+    def exePostRunActions(self):
+        self._logger.debug("Going to execute Post-Run commands...")
+        for cmd in self._postRunActions: cmd.postRunAction()
+        self._logger.debug("Done executing Post-Run commands")
+                
+
+    def execute(self):
+        """Execute transformation. Can only be called after the arguments have been set"""
+        #clean up old stuff
+        Filename.remove(trferr.exitStatusFile)
+
+        try:
+            # write the last command to file
+            self.writeLastCommand()
+            # set up runtime environment
+            trfenv.setup_runtime()
+            # first run pre-run actions, since they may change the values of the arguments
+            self.exePreRunActions()
+            # check that all arguments have a value
+            for arg in self._positionalArgs:
+                if not arg.hasValue():
+                    raise TransformArgumentError, '%s has no value' % (arg.name())
+
+            #get the needed top jobOptions files
+            self.ensureSkeleton()
+            self.writeRunArgs()
+            
+            # run the athena job
+            athena = os.path.join( trfenv.trfPath, 'athena_wrapper.py' )
+            syscommand = '%s %s %s' % ( athena, self.runArgsFilename(), self.skeletonFilename() )
+            self._logger.debug( 'Executing %s' % (syscommand) )
+            status = os.system( syscommand ) >> 8 # status code is in upper byte of 16 bit word
+            if status != 0:
+                if Filename.exists(trferr.exitStatusFile):
+                    # error file is already written in athena_wrapper.py
+                    sys.exit( status )
+                else:
+                    trferr.exit( status, 'Unhandled athena exit code' )
+
+            # do all postRunActions
+            self.exePostRunActions()
+            
+
+        # Catch all exceptions
+        except TransformError, e:
+            trferr.exit( e.exitStatus(), e.message )
+#        except exceptions.Exception, e:
+#            trferr.exit(trferr.UNKNOWN_EXCEPTION_RAISED, e.args)
+        else:
+            trferr.exit(trferr.TRF_OK)
+        
+
+        trferr.exit(trferr.UNKNOWN_ERROR)
+
+
+
+    def exeSysArgs(self):
+        """Execute transformation using the command line arguments"""
+        self._logger.debug( 'Using %s' % ( trfenv.trfPath) )
+        # process argument list
+        try:
+            self.processArgs(sys.argv[1:])
+        except TransformError, e:
+            trferr.exit( e.exitStatus(), e.message )
+            
+        # execute with filled arguments
+        self.execute()
+
+
+# prepare the runtime environment for the transformations
+import os,re
+from PyJobTransformsCore.trferr import *
+
+# no imports out of scope!
+__all__ = [ ] 
+
+athena_py = 'athena.py'
+trfPath = os.path.dirname(__file__)
+
+#print 'using %s' % os.path.dirname(trfPath)
+
+#some constants to avoid typing errors
+JOBOPTIONSPATH='JOBOPTSEARCHPATH'
+LD_PRELOAD = 'LD_PRELOAD'
+IFS = 'IFS'
+PYTHONPATH='PYTHONPATH'
+LD_LIBRARY_PATH='LD_LIBRARY_PATH'
+
+# setup the run-time environment
+def setup_runtime():
+    # check that LD_LIBRARY_PATH is set
+    try:
+        os.environ[LD_LIBRARY_PATH]
+    except KeyError:
+        raise TransformEnvironmentError( 'LD_LIBRARY_PATH not set' )
+    
+    # add current dir to jo path
+    try:
+        joPathEnv = os.environ[ JOBOPTIONSPATH ]
+    except KeyError:
+        jobOptionsPath = [ '' ]
+    else:
+        jobOptionsPath = re.split( ',|' + os.pathsep, joPathEnv )
+        if '' not in jobOptionsPath:
+            jobOptionsPath.insert(0, '')
+    os.environ[ JOBOPTIONSPATH ] = ','.join(jobOptionsPath)
+    #
+    # setting up some basic running environment (translated from athena.py)
+    #
+    ifs = os.environ.get(IFS,'')
+    ifs += ':'
+    os.environ[IFS] = ifs
+    hepmc=''
+    libHepMC_base_so = 'libHepMC_base.so'
+    for dir in os.environ[LD_LIBRARY_PATH].split(os.pathsep):
+        if os.path.exists( os.path.join( dir, libHepMC_base_so) ):
+            hepmc=libHepMC_base_so
+            break
+    # for customized gcc dyncast and workaround for the HepMC_base/CLHEP problem
+    # note: any other combination/order/syntax will crash valgrind 2.2.0
+    try:
+        ld_preload = os.environ[LD_PRELOAD].split(os.pathsep)
+    except KeyError:
+        ld_preload = [ ]
+
+    ld_preload += [ "libAthenaServices.so", "libGaudiKernel.so" ]
+    if hepmc: ld_preload += [ hepmc ]
+    os.environ[LD_PRELOAD] = os.pathsep.join(ld_preload)
+
+
+################################################################################
+# Exception classes
+################################################################################
+import exceptions,sys
+from AthenaCommon.Logging import logging
+from PyJobTransformsCore.trfutil import *
+
+__all__ = [ 'TransformError', 'TransformDefinitionError', 'TransformArgumentError',
+            'TransformEnvError', 'InputFileError', 'OutputFileError', 'TransformErrorHandler' ]
+
+exitStatusFile = 'exitstatus'
+
+TRF_OK = 0
+ATHENA_FAILURE_ERROR = 1
+ATHENA_LOAD_DLL_ERROR = 3
+JOBOPTIONS_NOT_FOUND = 5
+JOBOPTIONS_PYTHON_ERROR = 6
+TRF_INPUTFILE_ERROR = 20
+TRF_ARGUMENT_ERROR = 30
+TRF_DEFINITION_ERROR = 31
+TRF_ENVIRONMENT_ERROR = 32
+TRF_OUTPUTFILE_ERROR = 40
+UNKNOWN_EXCEPTION_RAISED = 254
+UNKWOWN_ERROR = 255
+
+messages = { TRF_OK : 'OK' ,
+             ATHENA_FAILURE_ERROR     : 'AthenaExitWithFailure'  ,
+             ATHENA_LOAD_DLL_ERROR    : 'AthenaFailLoadLibrary' ,
+             JOBOPTIONS_NOT_FOUND  : 'JobOptionsNotFound' ,
+             JOBOPTIONS_PYTHON_ERROR : 'JobOptionsPythonError' ,
+             TRF_INPUTFILE_ERROR   : 'TransformInputFileError'   ,
+             TRF_ARGUMENT_ERROR    : 'TransformArgumentError'    ,
+             TRF_DEFINITION_ERROR  : 'TransformDefinitionError'  ,
+             TRF_ENVIRONMENT_ERROR : 'TransformEnvironmentError' ,
+             TRF_OUTPUTFILE_ERROR  : 'TransformOutputFileError' ,
+             UNKNOWN_EXCEPTION_RAISED : 'UnknownExceptionError'  ,
+             UNKWOWN_ERROR            : 'UnknownError' }
+
+
+def exit(status=0,message=None):
+    statinfo = '%s %s' % (status,messages.get(status,'UnknownErrorCode'))
+    if message: statinfo += ' # %s' % (message)
+    if status: print '