Salome HOME
Porting to python3
[tools/yacsgen.git] / module_generator / pycompo.py
1 # Copyright (C) 2009-2016  EDF R&D
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 """
21   Module that defines PYComponent for SALOME components implemented in Python
22 """
23 import os
24 from module_generator.gener import Component, Invalid
25 from module_generator.pyth_tmpl import pyinitService, pyService, pyCompoEXE, pyCompo, cmake_src_compo_py
26 import textwrap
27 from string import split,rstrip,join
28
29 def indent(text, prefix='    '):
30   """Indent text by prepending a given prefix to each line."""
31   if not text: return ''
32   lines = split(text, '\n')
33   lines = list(map(lambda line, prefix=prefix: prefix + line, lines))
34   if lines: lines[-1] = rstrip(lines[-1])
35   return join(lines, '\n')
36
37 class PYComponent(Component):
38   """
39    A :class:`PYComponent` instance represents a Python SALOME component with services given as a list of :class:`Service`
40    instances with the parameter *services*.
41
42    :param name: gives the name of the component.
43    :type name: str
44    :param services: the list of services (:class:`Service`) of the component.
45    :param kind: If it is given and has the value "exe", the component will be built as a standalone
46       component (python executable). The default is to build the component as a python module.
47    :param sources: gives all the external Python source files to add in the component directory (list of paths).
48    :param python_path: If it is given (as a list of paths), all the paths are added to the python path (sys.path).
49    :param compodefs: can be used to add extra definition code in the component for example when using a base class
50       to define the component class by deriving it (see *inheritedclass* parameter)
51    :param inheritedclass: can be used to define a base class for the component. The base class can be defined in external
52       source or with the *compodefs* parameter. The value of the *inheritedclass* parameter is the name of the base class.
53    :param idls: can be used to add extra idl CORBA interfaces. This parameter must gives a list of idl file names that are
54       added into the generated module (idl directory) and compiled with the generated idl of the module.
55    :param interfacedefs: can be used to add idl definitions (or includes of idl files) into the generated idl of the module.
56    :param inheritedinterface: can be used to make the component inherit an extra idl interface that has been included through
57       the *idls* and *interfacedefs* parameters. See the pygui1 example for how to use these last parameters.
58
59    For example, the following call defines a Python component named "mycompo" with one service s1 (it must have been defined before)::
60
61       >>> c1 = module_generator.PYComponent('mycompo', services=[s1,],
62                                                        python_path="apath")
63
64   """
65   def __init__(self, name, services=None, kind="lib", sources=None, python_path=None,
66                      compodefs="", inheritedclass="", idls=None, interfacedefs="", inheritedinterface=""):
67     """initialise component attributes"""
68     self.python_path = python_path or []
69     Component.__init__(self, name, services, impl="PY", kind=kind, sources=sources,
70                              inheritedclass=inheritedclass, compodefs=compodefs,
71                              idls=idls,interfacedefs=interfacedefs,inheritedinterface=inheritedinterface)
72
73   def validate(self):
74     """validate component attributes"""
75     Component.validate(self)
76     kinds = ("lib","exe")
77     if self.kind not in kinds:
78       raise Invalid("kind must be one of %s for component %s" % (kinds,self.name))
79
80   def libraryName(self):
81     """ Name of the target library
82         No library for a python component
83     """
84     return ""
85     
86   def makeCompo(self, gen):
87     """generate component sources as a dictionary containing
88        file names (key) and file content (values)
89     """
90     pyfile = ""
91     file_content = ""
92     if self.kind == "lib":
93       pyfile = self.name  + ".py"
94       file_content = self.makepy(gen)
95     elif self.kind == "exe":
96       pyfile = self.name + ".exe"
97       file_content = self.makepyexe(gen)
98     else :
99       raise Invalid("Invalid kind ()%s for component %s" % (
100                                     self.kind, self.name))
101     
102     sources = pyfile + "".join(["\n  " + os.path.basename(x) for x in self.sources])
103     cmake_content = cmake_src_compo_py.substitute(sources=sources)
104     
105     return {"CMakeLists.txt":cmake_content,
106             pyfile:file_content
107            }
108
109   def makepy(self, gen):
110     """generate standard SALOME component source (python module)"""
111     services = []
112     inits = []
113     defs = []
114     for serv in self.services:
115       defs.append(serv.defs)
116       params = []
117       pyparams = []
118       for name, typ in serv.inport:
119         if typ=="file":continue #files are not passed through service interface
120         params.append(name)
121         if typ == "pyobj":
122           pyparams.append("      %s=cPickle.loads(%s)" %(name, name))
123       inparams = ",".join(params)
124       convertinparams = '\n'.join(pyparams)
125
126       params = []
127       pyparams = []
128       for name, typ in serv.outport:
129         if typ=="file":continue #files are not passed through service interface
130         params.append(name)
131         if typ == "pyobj":
132           pyparams.append("      %s=cPickle.dumps(%s,-1)" %(name, name))
133       outparams = ",".join(params)
134       convertoutparams = '\n'.join(pyparams)
135       #dedent and indent the body
136       body=textwrap.dedent(serv.body)
137       body=indent(body,' '*6)
138
139       service = pyService.substitute(component=self.name, service=serv.name, inparams=inparams,
140                                      outparams=outparams, body= body,
141                                      convertinparams=convertinparams,
142                                      convertoutparams=convertoutparams)
143       streams = []
144       for name, typ, dep in serv.instream:
145         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","IN","%s")'% (name, typ, dep))
146       instream = "\n".join(streams)
147       streams = []
148       for name, typ, dep in serv.outstream:
149         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","OUT","%s")'% (name, typ, dep))
150       outstream = "\n".join(streams)
151
152       init = pyinitService.substitute(component=self.name, service=serv.name,
153                                       instream=instream, outstream=outstream)
154       services.append(service)
155       inits.append(init)
156
157     python_path = ",".join([repr(p) for p in self.python_path])
158
159     inheritedclass=self.inheritedclass
160     callconstructor=""
161     if self.inheritedclass:
162       inheritedclass= self.inheritedclass + ","
163       callconstructor="""
164     if hasattr(%s,"__init__"):
165       %s.__init__(self)""" % (self.inheritedclass,self.inheritedclass)
166
167     return pyCompo.substitute(component=self.name, module=gen.module.name,
168                               servicesdef="\n".join(defs), servicesimpl="\n".join(services),
169                               initservice='\n'.join(inits),
170                               python_path=python_path,inheritedclass=inheritedclass,
171                               compodefs=self.compodefs, callconstructor=callconstructor)
172
173   def makepyexe(self, gen):
174     """generate standalone component source (python executable)"""
175     services = []
176     inits = []
177     defs = []
178     for serv in self.services:
179       defs.append(serv.defs)
180       params = []
181       pyparams = []
182       for name, typ in serv.inport:
183         if typ=="file":continue #files are not passed through service interface
184         params.append(name)
185         if typ == "pyobj":
186           pyparams.append("      %s=cPickle.loads(%s)" %(name, name))
187       inparams = ",".join(params)
188       convertinparams = '\n'.join(pyparams)
189
190       params = []
191       pyparams = []
192       for name, typ in serv.outport:
193         if typ=="file":continue #files are not passed through service interface
194         params.append(name)
195         if typ == "pyobj":
196           pyparams.append("      %s=cPickle.dumps(%s,-1)" %(name, name))
197       outparams = ",".join(params)
198       convertoutparams = '\n'.join(pyparams)
199       #dedent and indent the body
200       body=textwrap.dedent(serv.body)
201       body=indent(body,' '*6)
202       service = pyService.substitute(component=self.name, service=serv.name,
203                                      inparams=inparams, outparams=outparams,
204                                      body=body,
205                                      convertinparams=convertinparams,
206                                      convertoutparams=convertoutparams,
207                                     )
208       streams = []
209       for name, typ, dep in serv.instream:
210         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","IN","%s")'% (name, typ, dep))
211       instream = "\n".join(streams)
212       streams = []
213       for name, typ, dep in serv.outstream:
214         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","OUT","%s")'% (name, typ, dep))
215       outstream = "\n".join(streams)
216
217       init = pyinitService.substitute(component=self.name, service=serv.name,
218                                       instream=instream, outstream=outstream)
219       services.append(service)
220       inits.append(init)
221
222     python_path = ",".join([repr(p) for p in self.python_path])
223
224     inheritedclass=self.inheritedclass
225     callconstructor=""
226     if self.inheritedclass:
227       inheritedclass= self.inheritedclass + ","
228       callconstructor="""
229     if hasattr(%s,"__init__"):
230       %s.__init__(self)""" % (self.inheritedclass,self.inheritedclass)
231
232     return pyCompoEXE.substitute(component=self.name, module=gen.module.name,
233                                  servicesdef="\n".join(defs),
234                                  servicesimpl="\n".join(services),
235                                  initservice='\n'.join(inits),
236                                  python_path=python_path,inheritedclass=inheritedclass,
237                                  compodefs=self.compodefs, callconstructor=callconstructor)
238