plugger.py
author etisserant
Tue, 28 Aug 2007 07:57:36 +0200
changeset 14 eb9fdd316a40
parent 13 f1f0edbeb313
child 15 7a473efc4530
permissions -rw-r--r--
More precise design for plugins.... to be continued...
"""
Base definitions for beremiz plugins
"""

import os
import types
import shutil
from xml.dom import minidom
import plugins
from xmlclass import GenerateClassesFromXSDstring

_BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
          <xsd:element name="BaseParams">
            <xsd:complexType>
              <xsd:attribute name="Name" type="xsd:string" use="required"/>
              <xsd:attribute name="IEC_Channel" type="xsd:integer" use="required"  default="-1"/>
              <xsd:attribute name="Enabled" type="xsd:boolean" use="required" default="true"/>
            </xsd:complexType>
          </xsd:element>
        </xsd:schema>""")[0]["BaseParams"]

NameTypeSeparator = '@'

class PlugTemplate:
    """
    This class is the one that define plugins.
    """

    XSD = None
    PlugChildsTypes = []
    PlugMaxCount = None
    PluginMethods = []

    def _AddParamsMembers(self):
        Classes = GenerateClassesFromXSDstring(self.XSD)[0]
        self.PlugParams = []
        for name, XSDclass in Classes.items():
            if XSDclass.IsBaseClass:
                obj = XSDclass()
                self.PlugParams.append( (name, obj) )
                setattr(self, name, obj)

    def __init__(self, PlugPath):
        # Create BaseParam 
        self.BaseParams = _BaseParamsClass()
        self.MandatoryParams = [("BaseParams", self.BaseParams)]
        self._AddParamsMembers()
        self.PluggedChilds = {}
    
    def PluginXmlFilePath(self, PlugName=None):
        return os.path.join(self.PlugPath(PlugName), "plugin.xml")

    def PlugPath(self,PlugName=None):
        if not PlugName:
            PlugName = self.BaseParams.getName()
        return os.path.join(self.PlugParent.PlugPath(), PlugName + NameTypeSeparator + self.PlugType)
    
    def PlugTestModified(self):
        return False
        
    def OnPlugSave(self):
        return True

    def PlugRequestSave(self):
        # If plugin do not have corresponding directory
        if not os.path.isdir(self.PlugPath(PlugName)):
            # Create it
            os.mkdir(self.PlugPath(PlugName))

        # generate XML for all XML parameters controllers of the plugin
        XMLString = '<?xml version="1.0" encoding="UTF-8"?>'
        for nodeName, XMLController in self.PlugParams + self.MandatoryParams:
            XMLString += XMLController.generateXMLTextMethod(self, nodeName, 0)
        XMLFile = open(self.PluginXmlFilePath(PlugName),'w')
        XMLFile.write(XMLString)
        XMLFile.close()
        
        # Call the plugin specific OnPlugSave method
        self.OnPlugSave()
        
        # go through all childs and do the same
        for PlugChild in self.IterChilds():
            PlugChild.PlugRequestSave()
    
    def PlugImport(self, src_PlugPath):
        shutil.copytree(src_PlugPath, self.PlugPath)
        return True

    def PlugGenerate_C(self, buildpath, current_location, locations):
        """
        Generate C code
        @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
        @param locations: List of complete variables locations \
            [(IEC_loc, IEC_Direction IEC_Type, Name)]\
            ex: [((0,0,4,5),'I','X','__IX_0_0_4_5'),...]
        """
        return [],""
    
    def _Generate_C(self, buildpath, current_location, locations):
        # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS
        PlugCFilesAndCFLAGS, PlugLDFLAGS = self._Generate_C(buildpath, current_location, locations)
        # recurse through all childs, and stack their results
        for PlugChild in self.IterChilds():
            # Get childs [(Cfiles, CFLAGS)], LDFLAGS
            CFilesAndCFLAGS, LDFLAGS = \
                PlugChild._Generate_C(
                    #keep the same path
                    buildpath,
                    # but update location (add curent IEC channel at the end)
                    current_location + (self.BaseParams.getIEC_Channel()),
                    # filter locations that start with current IEC location
                    [ (l,d,t,n) for l,d,t,n in locations if l[0:len(current_location)] == current_location ])
            # stack the result
            PlugCFilesAndCFLAGS += CFilesAndCFLAGS
            PlugLDFLAGS += LDFLAGS
        
        return PlugCFilesAndCFLAGS,PlugLDFLAGS

    def BlockTypesFactory(self):
        return []

    def STLibraryFactory(self):
        return ""

    def IterChilds(self):
        for PlugType, PluggedChilds in self.PluggedChilds.items():
            for PlugInstance in PluggedChilds:
                   yield PlugInstance
    
    def _GetChildBySomething(self, sep, something, matching):
        toks = matching.split(sep,1)
        for PlugInstance in self.IterChilds:
            # if match component of the name
            if getattr(PlugInstance.BaseParams, something) == toks[0]:
                # if Name have other components
                if len(toks) == 2:
                    # Recurse in order to find the latest object
                    return PlugInstance._GetChildBySomething( sep, something, toks[1])
                # No sub name -> found
                return PlugInstance
        # Not found
        return None

    def GetChildByName(self, Name):
        return self._GetChildBySomething('.',"Name", Name)

    def GetChildByIECLocation(self, Location):
        return self._GetChildBySomething('_',"IEC_Channel", Name)
    
    def FindNewIEC_Channel(self, DesiredChannel):
        """
        Changes IEC Channel number to DesiredChannel if available, nearest available if not.
        @param DesiredChannel: The desired IEC channel (int)
        """
        # Get Current IEC channel
        CurrentChannel = self.BaseParams.getIEC_Channel()
        # Do nothing if no change
        if CurrentChannel == DesiredChannel: return CurrentChannel
        # Build a list of used Channels out of parent's PluggedChilds
        AllChannels=[]
        for PlugInstance in self.PlugParent.IterChilds():
            if PlugInstance != self:
                AllChannels.append(PlugInstance.BaseParams.getIEC_Channel())
        AllChannels.sort()

        # Now, try to guess the nearest available channel
        res = DesiredChannel
        while res in AllChannels: # While channel not free
            if res < CurrentChannel: # Want to go down ?
                res -=  1 # Test for n-1
                if res < 0 : return CurrentChannel # Can't go bellow 0, do nothing
            else : # Want to go up ?
                res +=  1 # Test for n-1
        # Finally set IEC Channel
        self.BaseParams.setIEC_Channel(res)
        return res

    def OnPlugClose(self):
        return True

    def _doRemoveChild(self, PlugInstance):
        # Remove all childs of child
        for SubPlugInstance in PlugInstance.IterChilds():
            PlugInstance._doRemoveChild(SubPlugInstance)
        # Call the OnCloseMethod
        PlugInstance.OnPlugClose()
        # Delete plugin dir
        shutil.rmtree(PlugInstance.PlugPath())
        # Remove child of PluggedChilds
        self.PluggedChilds[PlugInstance.PlugType].remove(PlugInstance)
        # Forget it... (View have to refresh)

    def PlugRemoveChild(self, PlugName):
        # Fetch the plugin
        PlugInstance = self.GetChildByName(PlugName)
        # Ask to his parent to remove it
        PlugInstance.PlugParent._doRemoveChild(PlugInstance)

    def PlugAddChild(self, PlugName, PlugType):
        """
        Create the plugins that may be added as child to this node self
        @param PlugType: string desining the plugin class name (get name from PlugChildsTypes)
        @param PlugName: string for the name of the plugin instance
        """
        PlugChildsTypes = dict(self.PlugChildsTypes)
        # Check that adding this plugin is allowed
        try:
            PlugClass = PlugChildsTypes[PlugType]
        except KeyError:
            raise Exception, "Cannot create child %s of type %s "%(PlugName, PlugType)
        
        # if PlugClass is a class factory, call it. (prevent unneeded imports)
        if type(PlugClass) == types.FunctionType:
            PlugClass = PlugClass()
        
        # Eventualy Initialize child instance list for this class of plugin
        PluggedChildsWithSameClass = self.PluggedChilds.setdefault(PlugType,list())
        # Check count
        if PlugClass.MaxCount and len(PluggedChildsWithSameClass) >= PlugClass.MaxCount:
            raise Exception, "Max count (%d) reached for this plugin of type %s "%(PlugClass.MaxCount, PlugType)
        
        # create the final class, derived of provided plugin and template
        class FinalPlugClass(PlugClass, PlugTemplate):
            """
            Plugin class is derivated into FinalPlugClass before being instanciated
            This way __init__ is overloaded to ensure PlugTemplate.__init__ is called 
            before PlugClass.__init__, and to do the file related stuff.
            """
            def __init__(_self):
                # self is the parent
                _self.PlugParent = self
                # Keep track of the plugin type name
                _self.PlugType = PlugType
                # Call the base plugin template init - change XSD into class members
                PlugTemplate.__init__(_self)
                # If dir have already be made, and file exist
                if os.path.isdir(_self.PlugPath(PlugName)) and os.path.isfile(_self.PluginXmlFilePath(PlugName)):
                    #Load the plugin.xml file into parameters members
                    _self.LoadXMLParams()
                    # Call the plugin real __init__
                    PlugClass.__init__(_self)
                    #Load and init all the childs
                    _self.LoadChilds()
                    # Check that IEC_Channel is not already in use.
                    self.FindNewIEC_Channel(self.BaseParams.getIEC_Channel())
                else:
                    # If plugin do not have corresponding file/dirs - they will be created on Save
                    # Set plugin name
                    _self.BaseParams.setName(PlugName)
                    # Find an IEC number
                    _self.FindNewIEC_Channel(0)
                    # Call the plugin real __init__
                    PlugClass.__init__(_self)

        # Create the object out of the resulting class
        newPluginOpj = FinalPlugClass()
        # Store it in PluggedChils
        PluggedChildsWithSameClass.append(newPluginOpj)
        
        return newPluginOpj
            

    def LoadXMLParams(self):
        # PlugParams have been filled, make a local dict to work with
        PlugParams = dict(self.PlugParams + self.MandatoryParams)
        # Get the xml tree
        xmlfile = open(self.PluginXmlFilePath(PlugName), 'r')
        tree = minidom.parse(xmlfile)
        xmlfile.close()
        # for each root elements
        for subtree in tree.childNodes:
            # if a plugin specific parameter set
            if subtree.nodeName in PlugParams:
                #Load into associated xmlclass.
                PlugParams[subtree.nodeName].loadXMLTree(subtree)
        
        # Basic check. Better to fail immediately.
        if(self.BaseParams.getName() != PlugName):
            raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(PlugName,self.BaseParams.getName())
        # Now, self.PlugPath() should be OK

    def LoadChilds(self):
        # Iterate over all PlugName@PlugType in plugin directory, and try to open them
        for PlugDir in os.listdir(self.PlugPath()):
            if os.path.isdir(os.path.join(self.PlugPath(),PlugDir)) and \
               PlugDir.count(NameTypeSeparator) == 1:
                try:
                    self.PlugAddChild(*PlugDir.split[NameTypeSeparator])
                except Exception, e:
                    print e


class PluginsRoot(PlugTemplate):

    # For root object, available Childs Types are modules of the plugin packages.
    PlugChildsTypes = [(name,lambda : getattr(__import__("plugins." + name), name).RootClass) for name in plugins.__all__]

    XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:simpleType name="Win32Compiler">
        <xsd:restriction base="xsd:string">
          <xsd:enumeration value="Cygwin"/>
          <xsd:enumeration value="MinGW"/>
          <xsd:enumeration value="VC++"/>
        </xsd:restriction>
      </xsd:simpleType>
      <xsd:element name="BeremizRoot">
        <xsd:complexType>
          <xsd:element name="TargetType">
            <xsd:complexType>
              <xsd:choice>
                <xsd:element name="Win32">
                  <xsd:complexType>
                    <xsd:attribute name="ToolChain" type="ppx:Win32Compiler" use="required" default="MinGW"/>
                    <xsd:attribute name="Priority" type="xsd:integer" use="required" default="0"/>
                  </xsd:complexType>
                </xsd:element>
                <xsd:element name="Linux">
                  <xsd:complexType>
                    <xsd:attribute name="Compiler" type="xsd:string" use="required" default="gcc"/>
                    <xsd:attribute name="Nice" type="xsd:integer" use="required" default="0"/>
                  </xsd:complexType>
                </xsd:element>
                <xsd:element name="Xenomai">
                  <xsd:complexType>
                    <xsd:attribute name="xeno-config" type="xsd:string" use="required" default="/usr/xenomai/"/>
                    <xsd:attribute name="Compiler" type="xsd:string" use="required" default="0"/>
                    <xsd:attribute name="Priority" type="xsd:integer" use="required" default="0"/>
                  </xsd:complexType>
                </xsd:element>
                <xsd:element name="RTAI">
                  <xsd:complexType>
                    <xsd:attribute name="xeno-config" type="xsd:string" use="required" default="0"/>
                    <xsd:attribute name="Compiler" type="xsd:string" use="required" default="0"/>
                    <xsd:attribute name="Priority" type="xsd:integer" use="required" default="0"/>
                  </xsd:complexType>
                </xsd:element>
                <xsd:element name="Library">
                  <xsd:complexType>
                    <xsd:attribute name="Dynamic" type="xsd:boolean" default="true"/>
                    <xsd:attribute name="Compiler" type="xsd:string" use="required" default="0"/>
                  </xsd:complexType>
                </xsd:element>
              </xsd:choice>
            </xsd:complexType>
          </xsd:element>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
    """

    def __init__(self, ProjectPath):
        # self is the parent
        self.PlugParent = None
        # Keep track of the plugin type name
        self.PlugType = "Beremiz"
        # Keep track of the root plugin (i.e. project path)
        self.ProjectPath = ProjectPath
        # Change XSD into class members
        self._AddParamsMembers()
        self.PluggedChilds = {}
        # No IEC channel, name, etc...
        self.MandatoryParams = []
        # If dir have already be made, and file exist
        if os.path.isdir(_self.PlugPath(PlugName)) and os.path.isfile(_self.PluginXmlFilePath(PlugName)):
            #Load the plugin.xml file into parameters members
            _self.LoadXMLParams()
            #Load and init all the childs
            _self.LoadChilds()

    def PlugPath(self,PlugName=None):
        return self.ProjectPath
        
    def PluginXmlFilePath(self, PlugName=None):
        return os.path.join(self.PlugPath(PlugName), "beremiz.xml")