SFrame 3.6
python/CycleCreators.py
Go to the documentation of this file.
00001 # $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $
00002 ###########################################################################
00003 # @Project: SFrame - ROOT-based analysis framework for ATLAS              #
00004 #                                                                         #
00005 # @author Stefan Ask       <Stefan.Ask@cern.ch>           - Manchester    #
00006 # @author David Berge      <David.Berge@cern.ch>          - CERN          #
00007 # @author Johannes Haller  <Johannes.Haller@cern.ch>      - Hamburg       #
00008 # @author A. Krasznahorkay <Attila.Krasznahorkay@cern.ch> - CERN/Debrecen #
00009 #                                                                         #
00010 ###########################################################################
00011 
00012 ## @package CycleCreators
00013 #    @short Functions for creating a new analysis cycle torso
00014 #
00015 # This package collects the functions used by sframe_create_cycle.py
00016 # to create the torso of a new analysis cycle. Apart from using
00017 # sframe_create_cycle.py, the functions can be used in an interactive
00018 # python session by executing:
00019 #
00020 # <code>
00021 #  >>> import CycleCreators
00022 # </code>
00023 
00024 ## @short Class creating analysis cycle templates
00025 #
00026 # This class can be used to create a template cycle inheriting from
00027 # SCycleBase. It is quite smart actually. If you call CycleCreator.CreateCycle
00028 # from inside an "SFrame package", it will find the right locations for the
00029 # created files and extend an already existing LinkDef.h file with the
00030 # line for the new cycle.
00031 class CycleCreator:
00032 
00033     _headerFile = ""
00034     _sourceFile = ""
00035 
00036     def __init__( self ):
00037         self._headerFile = ""
00038         self._sourceFile = ""
00039 
00040     ## @short Template for cycle outside of a namespace
00041     #
00042     # This string is used by CreateHeader to create a header file that
00043     # holds a cycle which is not in a namespace.
00044     _header = """// Dear emacs, this is -*- c++ -*-
00045 // $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $
00046 #ifndef %(class)-s_H
00047 #define %(class)-s_H
00048 
00049 // SFrame include(s):
00050 #include \"core/include/SCycleBase.h\"
00051 
00052 /**
00053  *   @short Put short description of class here
00054  *
00055  *          Put a longer description over here...
00056  *
00057  *  @author Put your name here
00058  * @version $Revision: 173 $
00059  */
00060 class %(class)-s : public SCycleBase {
00061 
00062 public:
00063    /// Default constructor
00064    %(class)-s();
00065    /// Default destructor
00066    ~%(class)-s();
00067 
00068    /// Function called at the beginning of the cycle
00069    virtual void BeginCycle() throw( SError );
00070    /// Function called at the end of the cycle
00071    virtual void EndCycle() throw( SError );
00072 
00073    /// Function called at the beginning of a new input data
00074    virtual void BeginInputData( const SInputData& ) throw( SError );
00075    /// Function called after finishing to process an input data
00076    virtual void EndInputData  ( const SInputData& ) throw( SError );
00077 
00078    /// Function called after opening each new input file
00079    virtual void BeginInputFile( const SInputData& ) throw( SError );
00080 
00081    /// Function called for every event
00082    virtual void ExecuteEvent( const SInputData&, Double_t ) throw( SError );
00083 
00084 private:
00085    //
00086    // Put all your private variables here
00087    //
00088 
00089    // Macro adding the functions for dictionary generation
00090    ClassDef( %(class)-s, 0 );
00091 
00092 }; // class %(class)-s
00093 
00094 #endif // %(class)-s_H
00095 
00096 """
00097 
00098     ## @short Template for cycle in a namespace
00099     #
00100     # This string is used by CreateHeader to create a header file that
00101     # holds a cycle which is in a namespace.
00102     _headerNs = """// Dear emacs, this is -*- c++ -*-
00103 // $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $
00104 #ifndef %(ns)-s_%(class)-s_H
00105 #define %(ns)-s_%(class)-s_H
00106 
00107 // SFrame include(s):
00108 #include \"core/include/SCycleBase.h\"
00109 
00110 namespace %(ns)-s {
00111 
00112    /**
00113     *   @short Put short description of class here
00114     *
00115     *          Put a longer description over here...
00116     *
00117     *  @author Put your name here
00118     * @version $Revision: 173 $
00119     */
00120    class %(class)-s : public SCycleBase {
00121 
00122    public:
00123       /// Default constructor
00124       %(class)-s();
00125       /// Default destructor
00126       ~%(class)-s();
00127 
00128       /// Function called at the beginning of the cycle
00129       virtual void BeginCycle() throw( SError );
00130       /// Function called at the end of the cycle
00131       virtual void EndCycle() throw( SError );
00132 
00133       /// Function called at the beginning of a new input data
00134       virtual void BeginInputData( const SInputData& ) throw( SError );
00135       /// Function called after finishing to process an input data
00136       virtual void EndInputData  ( const SInputData& ) throw( SError );
00137 
00138       /// Function called after opening each new input file
00139       virtual void BeginInputFile( const SInputData& ) throw( SError );
00140 
00141       /// Function called for every event
00142       virtual void ExecuteEvent( const SInputData&, Double_t ) throw( SError );
00143 
00144    private:
00145       //
00146       // Put all your private variables here
00147       //
00148 
00149       // Macro adding the functions for dictionary generation
00150       ClassDef( %(ns)-s::%(class)-s, 0 );
00151 
00152    }; // class %(class)-s
00153 
00154 } // namespace %(ns)-s
00155 
00156 #endif // %(ns)-s_%(class)-s_H
00157 
00158 """
00159 
00160     ## @short Function creating an analysis cycle header
00161     #
00162     # This function can be used to create the header file for a new analysis
00163     # cycle. It can correctly create the header file if the cycle name is
00164     # defined like "Ana::AnalysisCycle". In this case it creates a cycle
00165     # called "AnalysisCycle" that is in the C++ namespace "Ana". Multiple
00166     # namespaces such as "Ana::MyAna::AnalysisCycle" are not supported!
00167     #
00168     # @param cycleName Name of the analysis cycle. Can contain the namespace name.
00169     # @param fileName  Optional parameter with the output header file name
00170     def CreateHeader( self, cycleName, fileName = "" ):
00171 
00172         # Extract the namespace name if it has been specified:
00173         namespace = ""
00174         import re
00175         if re.search( "::", cycleName ):
00176             print "CreateHeader:: We're creating a cycle in a namespace"
00177             m = re.match( "(.*)::(.*)", cycleName )
00178             namespace = m.group( 1 )
00179             cycleName = m.group( 2 )
00180             print "CreateHeader:: Namespace name = " + namespace
00181 
00182         # Construct the file name if it has not been specified:
00183         if fileName == "":
00184             fileName = cycleName + ".h"
00185 
00186         # Some printouts:
00187         print "CreateHeader:: Cycle name     = " + cycleName
00188         print "CreateHeader:: File name      = " + fileName
00189         self._headerFile = fileName
00190 
00191         # Create a backup of an already existing header file:
00192         import os.path
00193         if os.path.exists( fileName ):
00194             print "CreateHeader:: File \"" + fileName + "\" already exists"
00195             print "CreateHeader:: Moving \"" + fileName + "\" to \"" + \
00196                   fileName + ".backup\""
00197             import shutil
00198             shutil.move( fileName, fileName + ".backup" )
00199 
00200         # Write the header file:
00201         output = open( fileName, "w" )
00202         if namespace == "":
00203             output.write( self._header % { 'class' : cycleName } )
00204         else:
00205             output.write( self._headerNs % { 'class' : cycleName,
00206                                              'ns'    : namespace } )
00207 
00208         return
00209 
00210     ## @short Template for cycle outside of a namespace
00211     #
00212     # This string is used by CreateSource to create a source file that
00213     # holds a cycle which is not in a namespace.
00214     _source = """// $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $
00215 
00216 // Local include(s):
00217 #include \"%(dir)-s/%(class)-s.h\"
00218 
00219 ClassImp( %(class)-s );
00220 
00221 %(class)-s::%(class)-s()
00222    : SCycleBase() {
00223 
00224    SetLogName( GetName() );
00225 }
00226 
00227 %(class)-s::~%(class)-s() {
00228 
00229 }
00230 
00231 void %(class)-s::BeginCycle() throw( SError ) {
00232 
00233    return;
00234 
00235 }
00236 
00237 void %(class)-s::EndCycle() throw( SError ) {
00238 
00239    return;
00240 
00241 }
00242 
00243 void %(class)-s::BeginInputData( const SInputData& ) throw( SError ) {
00244 
00245    return;
00246 
00247 }
00248 
00249 void %(class)-s::EndInputData( const SInputData& ) throw( SError ) {
00250 
00251    return;
00252 
00253 }
00254 
00255 void %(class)-s::BeginInputFile( const SInputData& ) throw( SError ) {
00256 
00257    return;
00258 
00259 }
00260 
00261 void %(class)-s::ExecuteEvent( const SInputData&, Double_t ) throw( SError ) {
00262 
00263    return;
00264 
00265 }
00266 
00267 """
00268 
00269     ## @short Template for cycle in a namespace
00270     #
00271     # This string is used by CreateSource to create a source file that
00272     # holds a cycle which is in a namespace.
00273     _sourceNs = """// $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $
00274 
00275 // Local include(s):
00276 #include \"%(dir)-s/%(class)-s.h\"
00277 
00278 ClassImp( %(ns)-s::%(class)-s );
00279 
00280 namespace %(ns)-s {
00281 
00282    %(class)-s::%(class)-s()
00283       : SCycleBase() {
00284 
00285       SetLogName( GetName() );
00286    }
00287 
00288    %(class)-s::~%(class)-s() {
00289 
00290    }
00291 
00292    void %(class)-s::BeginCycle() throw( SError ) {
00293 
00294       return;
00295 
00296    }
00297 
00298    void %(class)-s::EndCycle() throw( SError ) {
00299 
00300       return;
00301 
00302    }
00303 
00304    void %(class)-s::BeginInputData( const SInputData& ) throw( SError ) {
00305 
00306       return;
00307 
00308    }
00309 
00310    void %(class)-s::EndInputData( const SInputData& ) throw( SError ) {
00311 
00312       return;
00313 
00314    }
00315 
00316    void %(class)-s::BeginInputFile( const SInputData& ) throw( SError ) {
00317 
00318       return;
00319 
00320    }
00321 
00322    void %(class)-s::ExecuteEvent( const SInputData&, Double_t ) throw( SError ) {
00323 
00324       return;
00325 
00326    }
00327 
00328 } // namespace %(ns)-s
00329 
00330 """
00331 
00332     ## @short Function creating the analysis cycle source file
00333     #
00334     # This function creates the source file that works with the header created
00335     # by CreateHeader. It is important that CreateHeader is executed before
00336     # this function, as it depends on knowing where the header file is
00337     # physically. (To include it correctly in the source file.) It can
00338     # handle cycles in namespaces, just like CreateHeader. (The same
00339     # rules apply.)
00340     #
00341     # @param cycleName Name of the analysis cycle. Can contain the namespace name.
00342     # @param fileName  Optional parameter with the output source file name
00343     def CreateSource( self, cycleName, fileName = "" ):
00344 
00345         # Extract the namespace name if it has been specified:
00346         namespace = ""
00347         import re
00348         if re.search( "::", cycleName ):
00349             print "CreateSource:: We're creating a cycle in a namespace"
00350             m = re.match( "(.*)::(.*)", cycleName )
00351             namespace = m.group( 1 )
00352             cycleName = m.group( 2 )
00353             print "CreateSource:: Namespace name = " + namespace
00354 
00355         # Construct the file name if it has not been specified:
00356         if fileName == "":
00357             fileName = cycleName + ".cxx"
00358 
00359         # Some printouts:
00360         print "CreateSource:: Cycle name     = " + cycleName
00361         print "CreateSource:: File name      = " + fileName
00362         self._sourceFile = fileName
00363 
00364         # The following is a tricky part. Here I evaluate how the source file
00365         # will be able to include the previously created header file.
00366         # Probably a Python guru could've done it in a shorter way, but
00367         # at least it works.
00368         import os.path
00369         hdir = os.path.dirname( self._headerFile )
00370         sdir = os.path.dirname( self._sourceFile )
00371         prefix = os.path.commonprefix( [ self._headerFile, self._sourceFile ] )
00372 
00373         hdir = hdir.replace( prefix, "" )
00374         sdir = sdir.replace( prefix, "" )
00375 
00376         nup = sdir.count( "/" );
00377         nup = nup + 1
00378         dir = ""
00379         for i in range( 0, nup ):
00380             dir = dir.join( [ "../", hdir ] )
00381 
00382         # Create a backup of an already existing header file:
00383         if os.path.exists( fileName ):
00384             print "CreateHeader:: File \"" + fileName + "\" already exists"
00385             print "CreateHeader:: Moving \"" + fileName + "\" to \"" + \
00386                   fileName + ".backup\""
00387             import shutil
00388             shutil.move( fileName, fileName + ".backup" )
00389 
00390         # Write the source file:
00391         output = open( fileName, "w" )
00392         if namespace == "":
00393             output.write( self._source % { 'dir'   : dir,
00394                                            'class' : cycleName } )
00395         else:
00396             output.write( self._sourceNs % { 'dir'   : dir,
00397                                              'class' : cycleName,
00398                                              'ns'    : namespace } )
00399 
00400         return
00401 
00402     ## @short Function adding link definitions for rootcint
00403     #
00404     # Each new analysis cycle has to declare itself in a so called "LinkDef
00405     # file". This makes sure that rootcint knows that a dictionary should
00406     # be generated for this C++ class.
00407     #
00408     # This function is also quite smart. If the file name specified does
00409     # not yet exist, it creates a fully functionaly LinkDef file. If the
00410     # file already exists, it just inserts one line declaring the new
00411     # cycle into this file.
00412     #
00413     # @param cycleName Name of the analysis cycle. Can contain the namespace name.
00414     # @param fileName  Optional parameter with the LinkDef file name
00415     def AddLinkDef( self, cycleName, fileName = "LinkDef.h" ):
00416 
00417         import os.path
00418         if os.path.exists( fileName ):
00419             print "AddLinkDef:: Extending already existing file \"" + fileName + "\""
00420             # Read in the already existing file:
00421             output = open( fileName, "r" )
00422             lines = output.readlines()
00423             output.close()
00424 
00425             # Find the "#endif" line:
00426             endif_line = ""
00427             import re
00428             for line in lines:
00429                 if re.search( "#endif", line ):
00430                     endif_line = line
00431             if endif_line == "":
00432                 print "AddLinkDef:: ERROR File \"" + file + "\" is not in the right format!"
00433                 print "AddLinkDef:: ERROR Not adding link definitions!"
00434                 return
00435             index = lines.index( endif_line )
00436 
00437             # Add the line defining the current analysis cycle:
00438             lines.insert( index, "#pragma link C++ class %s+;\n" % cycleName )
00439             lines.insert( index + 1, "\n" )
00440 
00441             # Overwrite the file with the new contents:
00442             output = open( fileName, "w" )
00443             for line in lines:
00444                 output.write( line )
00445             output.close()
00446 
00447         else:
00448             # Create a new file and fill it with all the necessary lines:
00449             print "AddLinkDef:: Creating new file called \"" + fileName + "\""
00450             output = open( fileName, "w" )
00451             output.write( "// Dear emacs, this is -*- c++ -*-\n" )
00452             output.write( "// $Id: CycleCreators.py 173 2010-05-12 15:49:33Z krasznaa $\n\n" )
00453             output.write( "#ifdef __CINT__\n\n" )
00454             output.write( "#pragma link off all globals;\n" )
00455             output.write( "#pragma link off all classes;\n" )
00456             output.write( "#pragma link off all functions;\n\n" )
00457             output.write( "#pragma link C++ nestedclass;\n\n" )
00458             output.write( "#pragma link C++ class %s+;\n\n" % cycleName )
00459             output.write( "#endif // __CINT__\n" )
00460 
00461         return
00462 
00463     ## @short Function creating a configuration file for the new cycle
00464     #
00465     # This function is supposed to create an example configuration file
00466     # for the new cycle. It uses PyXML to write the configuration, and
00467     # exactly this causes a bit of trouble. PyXML is about the worst
00468     # XML implementation I ever came accross... There are tons of things
00469     # that it can't do. Not to mention the lack of any proper documentation.
00470     #
00471     # All in all, the resulting XML file is not too usable at the moment,
00472     # it's probably easier just copying one of the example cycles from
00473     # SFrame/user/config and adjusting it to the user's needs...
00474     #
00475     # @param cycleName Name of the analysis cycle. Can contain the namespace name.
00476     # @param fileName  Optional parameter with the configuration file name
00477     def CreateConfig( self, cycleName, fileName = "" ):
00478 
00479         # Extract the namespace name if it has been specified:
00480         namespace = ""
00481         import re
00482         if re.search( "::", cycleName ):
00483             print "CreateConfig:: We're creating a cycle in a namespace"
00484             m = re.match( "(.*)::(.*)", cycleName )
00485             namespace = m.group( 1 )
00486             cycleName = m.group( 2 )
00487             print "CreateConfig:: Namespace name = " + namespace
00488 
00489         # Construct the file name if it has not been specified:
00490         if fileName == "":
00491             fileName = cycleName + "_config.xml"
00492 
00493         # Some printouts:
00494         print "CreateConfig:: Cycle name     = " + cycleName
00495         print "CreateConfig:: File name      = " + fileName
00496 
00497         # Use PyXML for the configuration creation:
00498         import xml.dom.minidom
00499 
00500         doc = xml.dom.minidom.Document()
00501 
00502         doctype = xml.dom.minidom.DocumentType( "JobConfiguration" )
00503         doctype.publicId = ""
00504         doctype.systemId = "JobConfig.dtd"
00505         doc.doctype = doctype
00506 
00507         jobconfig = doc.createElement( "JobConfiguration" )
00508         doc.appendChild( jobconfig )
00509         jobconfig.setAttribute( "JobName", cycleName + "Job" )
00510         jobconfig.setAttribute( "OutputLevel", "INFO" )
00511 
00512         userlib = doc.createElement( "Library" )
00513         jobconfig.appendChild( userlib )
00514         userlib.setAttribute( "Name", "YourLibraryNameComesHere" )
00515 
00516         cycle = doc.createElement( "Cycle" )
00517         jobconfig.appendChild( cycle )
00518         cycle.setAttribute( "Name", cycleName )
00519         cycle.setAttribute( "OutputDirectory", "./" )
00520         cycle.setAttribute( "PostFix", "" )
00521         cycle.setAttribute( "TargetLumi", "1.0" )
00522 
00523         inputdata = doc.createElement( "InputData" )
00524         cycle.appendChild( inputdata )
00525         inputdata.setAttribute( "Type", "Data1" )
00526         inputdata.setAttribute( "Version", "Reco" )
00527         inputdata.setAttribute( "Lumi", "0.0" )
00528         inputdata.setAttribute( "NEventsMax", "-1" )
00529 
00530         infile = doc.createElement( "In" )
00531         inputdata.appendChild( infile )
00532         infile.setAttribute( "FileName", "YourInputFileComesHere" )
00533 
00534         userconf = doc.createElement( "UserConfig" )
00535         cycle.appendChild( userconf )
00536 
00537         confitem = doc.createElement( "Item" )
00538         userconf.appendChild( confitem )
00539         confitem.setAttribute( "Name", "NameOfUserProperty" )
00540         confitem.setAttribute( "Value", "ValueOfUserProperty" )
00541         
00542         output = open( fileName, "w" )
00543         output.write( doc.toprettyxml( encoding="UTF-8" ) )
00544 
00545         return
00546 
00547     ## @short Main analysis cycle creator function
00548     #
00549     # The users of this class should normally just use this function
00550     # to create a new analysis cycle.
00551     #
00552     # It only really needs to receive the name of the new cycle, it can guess
00553     # the name of the LinkDef file by itself if it has to. It calls all the
00554     # other functions of this class to create all the files for the new
00555     # cycle.
00556     #
00557     # @param cycleName Name of the analysis cycle. Can contain the namespace name.
00558     # @param linkdef Optional parameter with the name of the LinkDef file
00559     def CreateCycle( self, cycleName, linkdef = "" ):
00560 
00561         # If the specified name contains a namespace, get just the class name:
00562         className = cycleName
00563         import re
00564         if re.search( "::", cycleName ):
00565             m = re.match( ".*::(.*)", cycleName )
00566             className = m.group( 1 )
00567 
00568         # Check if a directory called "include" exists in the current directory.
00569         # If it does, put the new header in that directory. Otherwise leave it up
00570         # to the CreateHeader function to put it where it wants.
00571         import os.path
00572         if os.path.exists( "./include" ):
00573             self.CreateHeader( cycleName, "./include/" + className + ".h" )
00574 
00575             if linkdef == "":
00576                 import glob
00577                 filelist = glob.glob( "./include/*LinkDef.h" )
00578                 if len( filelist ) == 0:
00579                     print "CreateCycle:: WARNING There is no LinkDef file under ./include"
00580                     print "CreateCycle:: WARNING Creating one with the name ./include/LinkDef.h"
00581                     linkdef = "./include/LinkDef.h"
00582                 elif len( filelist ) == 1:
00583                     linkdef = filelist[ 0 ]
00584                 else:
00585                     print "CreateCycle:: ERROR Multiple header files ending in LinkDef.h"
00586                     print "CreateCycle:: ERROR I don't know which one to use..."
00587                     return
00588 
00589             self.AddLinkDef( cycleName, linkdef )
00590 
00591         else:
00592             self.CreateHeader( cycleName )
00593 
00594             if linkdef == "":
00595                 import glob
00596                 filelist = glob.glob( "*LinkDef.h" )
00597                 if len( filelist ) == 0:
00598                     print "CreateCycle:: Creating new LinkDef file: LinkDef.h"
00599                     linkdef = "LinkDef.h"
00600                 elif len( filelist ) == 1:
00601                     linkdef = filelist[ 0 ]
00602                 else:
00603                     print "CreateCycle:: ERROR Multiple header files ending in LinkDef.h"
00604                     print "CreateCycle:: ERROR I don't know which one to use..."
00605                     return
00606 
00607             self.AddLinkDef( cycleName, linkdef )
00608 
00609         # Check if a directory called "src" exists in the current directory.
00610         # If it does, put the new source in that directory. Otherwise leave it up
00611         # to the CreateSource function to put it where it wants.
00612         if os.path.exists( "./src" ):
00613             self.CreateSource( cycleName, "./src/" + className + ".cxx" )
00614         else:
00615             self.CreateSource( cycleName )
00616 
00617         # Check if a directory called "config" exists in the current directory.
00618         # If it does, put the new configuration in that directory. Otherwise leave it up
00619         # to the CreateConfig function to put it where it wants.
00620         if os.path.exists( "./config" ):
00621             self.CreateConfig( cycleName, "./config/" + className + "_config.xml" )
00622         else:
00623             self.CreateConfig( cycleName )
00624 
00625         return