Commit d936b141 authored by Jérome Perrin's avatar Jérome Perrin

jedi save point

parent 08c10242
...@@ -9,6 +9,7 @@ import inspect ...@@ -9,6 +9,7 @@ import inspect
logger = logging.getLogger("erp5.extension.Jedi") logger = logging.getLogger("erp5.extension.Jedi")
#logger.info('included plugin loaded') #logger.info('included plugin loaded')
import os
import jedi import jedi
import zope.dottedname.resolve import zope.dottedname.resolve
import time import time
...@@ -48,8 +49,6 @@ except ImportError: ...@@ -48,8 +49,6 @@ except ImportError:
def makeERP5Plugin(): def makeERP5Plugin():
logger.info('making erp5 plugin') logger.info('making erp5 plugin')
import os
from jedi.evaluate.context import ModuleContext from jedi.evaluate.context import ModuleContext
from jedi.evaluate.lazy_context import LazyTreeContext from jedi.evaluate.lazy_context import LazyTreeContext
from jedi.evaluate.helpers import get_str_or_none from jedi.evaluate.helpers import get_str_or_none
...@@ -119,7 +118,7 @@ def makeERP5Plugin(): ...@@ -119,7 +118,7 @@ def makeERP5Plugin():
return wrapper return wrapper
def import_module(self, callback): def not_used_import_module(self, callback):
""" """
Handle ERP5 dynamic modules import. Handle ERP5 dynamic modules import.
""" """
...@@ -484,10 +483,24 @@ from Products.PythonScripts.PythonScript import PythonScript # pylint: disable ...@@ -484,10 +483,24 @@ from Products.PythonScripts.PythonScript import PythonScript # pylint: disable
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
def SkinsTool_getStub(self): def SkinsTool_getClassSet(self):
portal = self.getPortalObject() portal = self.getPortalObject()
sources = [] class_set = set([])
print('SkinsTool_getStub start') # TODO: sort by default skin selection and use the ones registered in skin selections
for skin_folder in portal.portal_skins.objectValues():
for script in skin_folder.objectValues(spec=('Script (Python)',
'External Method')):
if not '_' in script.getId():
logger.debug('Skipping wrongly named script %s', script.getId())
continue
type_ = script.getId().split('_')[0]
class_set.add(type_)
return class_set
def SkinsTool_getStubForClass(self, class_name):
portal = self.getPortalObject()
line_list = []
SkinDefinition = namedtuple( SkinDefinition = namedtuple(
'SkinDefinition', 'id,docstring,type_comment,skin_folder,params') 'SkinDefinition', 'id,docstring,type_comment,skin_folder,params')
import parso import parso
...@@ -504,8 +517,11 @@ def SkinsTool_getStub(self): ...@@ -504,8 +517,11 @@ def SkinsTool_getStub(self):
logger.debug('Skipping wrongly named script %s', script.getId()) logger.debug('Skipping wrongly named script %s', script.getId())
continue continue
type_ = script.getId().split('_')[0] type_ = script.getId().split('_')[0]
if type_ != class_name:
continue
docstring = '"""External method"""' docstring = '"""External method"""'
params = '' params = ''
type_comment = ''
if script.meta_type == 'Script (Python)': if script.meta_type == 'Script (Python)':
body = script.body() body = script.body()
params = script.params() params = script.params()
...@@ -514,14 +530,14 @@ def SkinsTool_getStub(self): ...@@ -514,14 +530,14 @@ def SkinsTool_getStub(self):
icon_path = script.om_icons()[0]['path'] icon_path = script.om_icons()[0]['path']
icon_alt = script.om_icons()[0]['alt'] icon_alt = script.om_icons()[0]['alt']
docstring_first_line = ( docstring_first_line = (
"![{icon_alt}]({portal_url}/{icon_path}) " "![{icon_alt}]({portal_url}/{icon_path}) "
"[`{script_id}`]({portal_url}/portal_skins/{skin_folder_id}/{script_id}/manage_main)\n").format( "[`{script_id}`]({portal_url}/portal_skins/{skin_folder_id}/{script_id}/manage_main)\n"
).format(
icon_alt=icon_alt, icon_alt=icon_alt,
portal_url=portal.absolute_url(), portal_url=portal.absolute_url(),
icon_path=icon_path, icon_path=icon_path,
script_id=script.getId(), script_id=script.getId(),
skin_folder_id=skin_folder.getId() skin_folder_id=skin_folder.getId())
)
docstring = '"""{}"""'.format(docstring_first_line) docstring = '"""{}"""'.format(docstring_first_line)
module = grammar.parse(body) module = grammar.parse(body)
if next(iter(grammar.iter_errors(module)), None) is not None: if next(iter(grammar.iter_errors(module)), None) is not None:
...@@ -536,14 +552,18 @@ def SkinsTool_getStub(self): ...@@ -536,14 +552,18 @@ def SkinsTool_getStub(self):
if first_leaf.type == 'string': if first_leaf.type == 'string':
original_docstring = first_leaf.value original_docstring = first_leaf.value
if original_docstring.startswith("'''"): if original_docstring.startswith("'''"):
docstring = "'''{}\n{}".format(docstring_first_line, original_docstring[3:]) docstring = "'''{}\n{}".format(
docstring_first_line, original_docstring[3:])
elif original_docstring.startswith("'"): elif original_docstring.startswith("'"):
docstring = "'''{}\n{}''".format(docstring_first_line, original_docstring[1:]) docstring = "'''{}\n{}''".format(
docstring_first_line, original_docstring[1:])
elif original_docstring.startswith('"""'): elif original_docstring.startswith('"""'):
docstring = '"""{}\n{}'.format(docstring_first_line, original_docstring[3:]) docstring = '"""{}\n{}'.format(
docstring_first_line, original_docstring[3:])
elif original_docstring.startswith('"'): elif original_docstring.startswith('"'):
docstring = '"""{}\n{}""'.format(docstring_first_line, original_docstring[1:]) docstring = '"""{}\n{}""'.format(
docstring_first_line, original_docstring[1:])
skin_by_type[type_].append( skin_by_type[type_].append(
SkinDefinition( SkinDefinition(
script.getId(), script.getId(),
...@@ -553,12 +573,11 @@ def SkinsTool_getStub(self): ...@@ -553,12 +573,11 @@ def SkinsTool_getStub(self):
params)) params))
for type_, skins in skin_by_type.items(): for type_, skins in skin_by_type.items():
sources.append( line_list.append(
textwrap.dedent( textwrap.dedent(
"""\ """\
import erp5.portal_type import erp5.portal_type
from erp5 import portal_type from erp5 import portal_type
from erp5.portal_type import Organisation as erp5_portal_type_Person
class {class_name}: class {class_name}:
{docstring} {docstring}
...@@ -568,8 +587,9 @@ def SkinsTool_getStub(self): ...@@ -568,8 +587,9 @@ def SkinsTool_getStub(self):
docstring=safe_docstring("Skins for {}".format(type_)))) docstring=safe_docstring("Skins for {}".format(type_))))
for skin in skins: for skin in skins:
skin = skin # type: SkinDefinition skin = skin # type: SkinDefinition
sources.append( line_list.append(
# ( the comment is also here so that dedent keep indentation ) # the comment is also here so that dedent keep indentation, because this method block needs
# more indentation than class block
textwrap.dedent( textwrap.dedent(
"""\ """\
# {skin_id} in {skin_folder} # {skin_id} in {skin_folder}
...@@ -581,11 +601,10 @@ def SkinsTool_getStub(self): ...@@ -581,11 +601,10 @@ def SkinsTool_getStub(self):
params=skin.params, params=skin.params,
type_comment=skin.type_comment, type_comment=skin.type_comment,
docstring=skin.docstring)) docstring=skin.docstring))
print('SkinsTool_getStub finished') return "\n".join(line_list)
return "\n".join(sources)
def TypesTool_getStub(self): def TypesTool_getStub(self): # XXX useless
print('TypesTool_getStub start') print('TypesTool_getStub start')
portal = self.getPortalObject() portal = self.getPortalObject()
sources = [TypesTool_getStubHeader(self)] sources = [TypesTool_getStubHeader(self)]
...@@ -635,6 +654,7 @@ def TypesTool_getStubHeader(self): ...@@ -635,6 +654,7 @@ def TypesTool_getStubHeader(self):
''') ''')
# TODO: drop include_header
def TypeInformation_getStub(self, include_header=True): def TypeInformation_getStub(self, include_header=True):
# type: (ERP5TypeInformation) -> str # type: (ERP5TypeInformation) -> str
"""returns a .pyi stub file for this portal type """returns a .pyi stub file for this portal type
...@@ -643,6 +663,9 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -643,6 +663,9 @@ def TypeInformation_getStub(self, include_header=True):
""" """
portal = self.getPortalObject() portal = self.getPortalObject()
# TODO: getParentValue
# TODO: a class for magic things like getPortalObject ?
@WorkflowMethod.disable @WorkflowMethod.disable
def makeTempClass(): def makeTempClass():
# everything is allowed in portal trash so we create our # everything is allowed in portal trash so we create our
...@@ -664,10 +687,10 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -664,10 +687,10 @@ def TypeInformation_getStub(self, include_header=True):
parent_class_module = parent_class.__module__ parent_class_module = parent_class.__module__
include_private = False include_private = False
imports = [] imports = ['import erp5.portal_type'] # TODO: this can be a set (all imports)
header = "" header = ""
if include_header: if include_header:
header = portal.portal_types.TypesTool_getStubHeader() header = TypesTool_getStubHeader(self.getPortalObject().portal_types)
methods = [] methods = []
debug = "" debug = ""
method_template_template = """ {decorator}\n def {method_name}({method_args}) -> {return_type}:\n {docstring}""" method_template_template = """ {decorator}\n def {method_name}({method_args}) -> {return_type}:\n {docstring}"""
...@@ -680,6 +703,16 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -680,6 +703,16 @@ def TypeInformation_getStub(self, include_header=True):
return_type='str', return_type='str',
docstring=safe_docstring(self.getId()))) docstring=safe_docstring(self.getId())))
imports.append('from erp5.portal_type import ERP5Site')
methods.append(
method_template_template.format(
decorator='',
method_name='getPortalObject',
method_args="self",
return_type='ERP5Site',
docstring=safe_docstring(
getattr(temp_class.getPortalObject, '__doc__', None) or '...')))
# first class contain workflow and some constraints. # first class contain workflow and some constraints.
for property_name in sorted(vars(temp_class)): for property_name in sorted(vars(temp_class)):
if property_name[0] == '_' and not include_private: if property_name[0] == '_' and not include_private:
...@@ -853,6 +886,22 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -853,6 +886,22 @@ def TypeInformation_getStub(self, include_header=True):
safe_python_identifier(pc.__name__), prefixed_class_name)) safe_python_identifier(pc.__name__), prefixed_class_name))
base_classes.append(prefixed_class_name) base_classes.append(prefixed_class_name)
# everything can use ERP5Site_ skins
imports.append('from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site')
base_classes.append('skins_tool_ERP5Site')
base_classes.append(prefixed_class_name)
# XXX special methods for some tools
if self.getId() == 'Simulation Tool':
methods.append(
method_template_template.format(
decorator='',
method_name='getInventoryList',
method_args="self", # TODO
return_type='erp5.portal_type.Person',
docstring=safe_docstring('TODO =)')))
class_template = textwrap.dedent( class_template = textwrap.dedent(
"""\ """\
{header} {header}
...@@ -1108,20 +1157,22 @@ from Products.ERP5.ERP5Site import ERP5Site # pylint: disable=unused-import ...@@ -1108,20 +1157,22 @@ from Products.ERP5.ERP5Site import ERP5Site # pylint: disable=unused-import
def ERP5Site_getPortalStub(self): def ERP5Site_getPortalStub(self):
# type: (ERP5Site) -> str # type: (ERP5Site) -> str
# TODO:
module_stub_template = textwrap.dedent( module_stub_template = textwrap.dedent(
'''\ '''
def __{module_id}(self): @property
from erp5.portal_type import {module_class_name} def {module_id}(self):
return {module_class_name}() from erp5.portal_type import {module_class_name}
{module_id} = property(__{module_id}) return {module_class_name}()
''') ''')
tool_stub_template = textwrap.dedent( tool_stub_template = textwrap.dedent(
'''\ '''
def __{tool_id}(self): @property
{tool_import} def {tool_id}(self):
return {tool_class}() {tool_import}
{tool_id} = property(__{tool_id}) return {tool_class}()
''') ''')
source = [] source = []
for m in self.objectValues(): for m in self.objectValues():
if m.getPortalType().endswith('Module'): if m.getPortalType().endswith('Module'):
...@@ -1137,29 +1188,31 @@ def ERP5Site_getPortalStub(self): ...@@ -1137,29 +1188,31 @@ def ERP5Site_getPortalStub(self):
m.__class__.__module__, m.__class__.__module__,
tool_class,) tool_class,)
if m.getId() == 'portal_catalog': if m.getId() == 'portal_catalog':
tool_class = 'ICatalogTool' tool_class = 'ICatalogTool' # XXX these I prefix are stupid
tool_import = '' tool_import = 'from erp5.portal_type import ICatalogTool'
source.extend( source.extend(
tool_stub_template.format( tool_stub_template.format(
tool_id=m.getId(), tool_class=tool_class, tool_id=m.getId(), tool_class=tool_class,
tool_import=tool_import).splitlines()) tool_import=tool_import).splitlines(),
)
# TODO: tools with at least base categories for CategoryTool # TODO: tools with at least base categories for CategoryTool
return textwrap.dedent( return textwrap.dedent(
'''\ '''
from Products.ERP5Site.ERP5Site import ERP5Site from Products.ERP5Site.ERP5Site import ERP5Site as ERP5Site_parent_ERP5Site
class IPortalObject(ERP5Site): from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site
{} class ERP5Site(ERP5Site_parent_ERP5Site, skins_tool_ERP5Site):
''').format('\n '.join(source)) {}
''').format('\n '.join(source))
def CatalogTool_getStub(self): def CatalogTool_getStub(self):
portal = self.getPortalObject() # type: IPortalObject portal = self.getPortalObject()
brain_class_list = [] brain_class_list = []
imports = [] imports = []
brain_template = textwrap.dedent( brain_template = textwrap.dedent(
'''\ '''\
from typing import TypeVar, Generic from typing import TypeVar, Generic, List, Union
T = TypeVar('T') T = TypeVar('T')
class Brain(Generic[T]): class Brain(Generic[T]):
id: str id: str
...@@ -1178,7 +1231,7 @@ def CatalogTool_getStub(self): ...@@ -1178,7 +1231,7 @@ def CatalogTool_getStub(self):
return textwrap.dedent( return textwrap.dedent(
'''\ '''\
from Products.ERP5Catalog.Tool import ERP5CatalogTool from Products.ERP5Catalog.Tool.ERP5CatalogTool import ERP5CatalogTool
{brain_template} {brain_template}
CatalogToolAllTypes = Union[{all_types}] CatalogToolAllTypes = Union[{all_types}]
class ICatalogTool(ERP5CatalogTool): class ICatalogTool(ERP5CatalogTool):
...@@ -1189,3 +1242,156 @@ def CatalogTool_getStub(self): ...@@ -1189,3 +1242,156 @@ def CatalogTool_getStub(self):
''').format( ''').format(
brain_template=brain_template, all_types=all_types) brain_template=brain_template, all_types=all_types)
def ERP5Site_dumpModuleCode(self, component_or_script=None):
"""Save code in filesystem for jedi to use it.
Generate stubs for erp5.* dynamic modules and copy the in-ZODB modules
to files.
"""
def mkdir_p(path):
if not os.path.exists(path):
os.mkdir(path, 0o700)
portal = self.getPortalObject() # type: 'erp5.portal_type.ERP5Site'
module_dir = '/tmp/ahaha/erp5/' # TODO
mkdir_p(module_dir)
# generate erp5/__init__.py
with open(
os.path.join(module_dir, '__init__.py'),
'w',) as erp5__init__f:
for module in (
'portal_type',
'accessor_holder',
'skins_tool',
'component',):
erp5__init__f.write('from . import {module}\n'.format(module=module))
# TODO: accessor_holder real name ?
# TODO: component versions ?
mkdir_p(os.path.join(module_dir, module))
if module == 'portal_type':
with open(
os.path.join(
module_dir,
module,
'__init__.py',),
'w',) as module_f:
for ti in portal.portal_types.contentValues():
class_name = safe_python_identifier(ti.getId())
module_f.write(
'from .{class_name} import {class_name}\n'.format(
class_name=class_name))
with open(
os.path.join(
module_dir,
module,
'{class_name}.pyi'.format(class_name=class_name),
),
'w',
) as type_information_f:
type_information_f.write(
ti.TypeInformation_getStub().encode('utf-8'))
# tools XXX don't do here but in TypeInformation_getStub ?
#module_f.write('from .CatalogTool import CatalogTool')
module_f.write('from .ICatalogTool import ICatalogTool\n')
with open(
os.path.join(
module_dir,
module,
'ICatalogTool.pyi',),
'w',) as portal_f:
portal_f.write(
CatalogTool_getStub(self.getPortalObject().portal_catalog))
module_f.write('from .SimulationTool import SimulationTool\n')
# TOODO: simulation tool
# portal object
module_f.write('from .ERP5Site import ERP5Site\n')
module_f.write(
'from .ERP5Site import ERP5Site as IPortalObject\n'
) # XXX what name for portal ? -> ERP5Site !
with open(
os.path.join(
module_dir,
module,
'ERP5Site.pyi',),
'w',) as portal_f:
portal_f.write(ERP5Site_getPortalStub(self.getPortalObject()))
elif module == 'accessor_holder':
# TODO: real path is accessor_holder.something !?
with open(
os.path.join(module_dir, module, '__init__.py'),
'w',) as accessor_holder_f:
for ps in portal.portal_property_sheets.contentValues():
class_name = safe_python_identifier(ps.getId())
accessor_holder_f.write(
'from .{class_name} import {class_name}\n'.format(
class_name=class_name))
with open(
os.path.join(
module_dir,
module,
'{class_name}.pyi'.format(class_name=class_name),
),
'w',
) as property_sheet_f:
property_sheet_f.write(ps.PropertySheet_getStub().encode('utf-8'))
elif module == 'skins_tool':
skins_tool = portal.portal_skins
with open(
os.path.join(module_dir, module, '__init__.py'),
'w',) as skins_tool_f:
for class_name in SkinsTool_getClassSet(skins_tool):
skins_tool_f.write(
'from {class_name} import {class_name}\n'.format(
class_name=class_name))
with open(
os.path.join(
module_dir,
module,
'{}.pyi'.format(class_name),),
'w',
) as skin_f:
skin_f.write(
SkinsTool_getStubForClass(
skins_tool,
class_name,).encode('utf-8'))
elif module == 'component':
module_to_component_portal_type_mapping = {
'test': 'Test Component',
'document': 'Document Component',
'extension': 'Extension Component',
'tool': 'Tool Component',
'module': 'Module Component',
'interface': 'Interface Component',
}
with open(
os.path.join(module_dir, module, '__init__.py'),
'w',) as component_module__init__f:
for sub_module, portal_type in module_to_component_portal_type_mapping.items():
component_module__init__f.write(
'from . import {}\n'.format(sub_module))
mkdir_p(os.path.join(module_dir, module, sub_module))
with open(
os.path.join(module_dir, module, sub_module, '__init__.py'),
'w',
) as component_sub_module_init_f:
for brain in portal.portal_catalog(
portal_type=portal_type, validation_state=('validated',)):
component = brain.getObject()
component_sub_module_init_f.write(
"from {component_reference} import {component_reference}\n"
.format(component_reference=component.getReference()))
with open(
os.path.join(
module_dir,
module,
sub_module,
'{}.py'.format(component.getReference()),
),
'w',
) as component_f:
component_f.write(component.getTextContent()) #.encode('utf-8'))
return 'done'
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>ERP5Site_dumpModuleCode</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>Jedi</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_dumpModuleCode</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment