"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/Utilities/ModuleParser.py" (2 Sep 2020, 60759 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ModuleParser.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2003 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Parse a Python module file.
    8 
    9 <b>BUGS</b> (from pyclbr.py)
   10 <ul>
   11 <li>Code that doesn't pass tabnanny or python -t will confuse it, unless
   12   you set the module TABWIDTH variable (default 8) to the correct tab width
   13   for the file.</li>
   14 </ul>
   15 """
   16 
   17 
   18 import sys
   19 import os
   20 import importlib.machinery
   21 import re
   22 
   23 import Utilities
   24 from functools import reduce
   25 
   26 __all__ = ["Module", "Class", "Function", "Attribute", "RbModule",
   27            "readModule", "getTypeFromTypeName"]
   28 
   29 TABWIDTH = 4
   30 
   31 SEARCH_ERROR = 0
   32 PY_SOURCE = 1
   33 PTL_SOURCE = 128
   34 RB_SOURCE = 129
   35 
   36 SUPPORTED_TYPES = [PY_SOURCE, PTL_SOURCE, RB_SOURCE]
   37 TYPE_MAPPING = {
   38     "Python": PY_SOURCE,
   39     "Python3": PY_SOURCE,
   40     "MicroPython": PY_SOURCE,
   41     "Ruby": RB_SOURCE,
   42 }
   43 
   44 
   45 def getTypeFromTypeName(name):
   46     """
   47     Module function to determine the module type given the module type name.
   48     
   49     @param name module type name (string)
   50     @return module type or -1 for failure (integer)
   51     """
   52     if name in TYPE_MAPPING:
   53         return TYPE_MAPPING[name]
   54     else:
   55         return -1
   56 
   57 
   58 _py_getnext = re.compile(
   59     r"""
   60     (?P<Comment>
   61         \# .*? $   # ignore everything in comments
   62     )
   63     
   64 |   (?P<String>
   65         \""" (?P<StringContents1>
   66                [^"\\]* (?:
   67                             (?: \\. | "(?!"") )
   68                             [^"\\]*
   69                         )*
   70             )
   71         \"""
   72 
   73     |   ''' (?P<StringContents2>
   74                 [^'\\]* (?:
   75                             (?: \\. | '(?!'') )
   76                             [^'\\]*
   77                         )*
   78             )
   79         '''
   80 
   81     |   " [^"\\\n]* (?: \\. [^"\\\n]*)* "
   82 
   83     |   ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
   84 
   85     |   \#\#\# (?P<StringContents3>
   86                 [^#\\]* (?:
   87                             (?: \\. | \#(?!\#\#) )
   88                             [^#\\]*
   89                         )*
   90             )
   91         \#\#\#
   92     )
   93 
   94 |   (?P<Docstring>
   95         (?<= :) \s*
   96         [ru]? \""" (?P<DocstringContents1>
   97                 [^"\\]* (?:
   98                             (?: \\. | "(?!"") )
   99                             [^"\\]*
  100                         )*
  101             )
  102         \"""
  103 
  104     |   (?<= :) \s*
  105         [ru]? ''' (?P<DocstringContents2>
  106                 [^'\\]* (?:
  107                             (?: \\. | '(?!'') )
  108                             [^'\\]*
  109                         )*
  110             )
  111         '''
  112 
  113     |   (?<= :) \s*
  114         \#\#\# (?P<DocstringContents3>
  115                 [^#\\]* (?:
  116                             (?: \\. | \#(?!\#\#) )
  117                             [^#\\]*
  118                         )*
  119             )
  120         \#\#\#
  121     )
  122 
  123 |   (?P<MethodModifier>
  124         ^
  125         (?P<MethodModifierIndent> [ \t]* )
  126         (?P<MethodModifierType> @classmethod | @staticmethod )
  127     )
  128 
  129 |   (?P<Method>
  130         (^ [ \t]* @ (?: PyQt[45] \. )? (?: QtCore \. )?
  131             (?: pyqtSignature | pyqtSlot )
  132             [ \t]* \(
  133                 (?P<MethodPyQtSignature> [^)]* )
  134             \) \s*
  135         )?
  136         ^
  137         (?P<MethodIndent> [ \t]* )
  138         (?: async [ \t]+ )? def [ \t]+
  139         (?P<MethodName> \w+ )
  140         (?: [ \t]* \[ (?: plain | html ) \] )?
  141         [ \t]* \(
  142         (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? )
  143         \) [ \t]*
  144         (?P<MethodReturnAnnotation> (?: -> [ \t]* [^:]+ )? )
  145         [ \t]* :
  146     )
  147 
  148 |   (?P<Class>
  149         ^
  150         (?P<ClassIndent> [ \t]* )
  151         class [ \t]+
  152         (?P<ClassName> \w+ )
  153         [ \t]*
  154         (?P<ClassSupers> \( [^)]* \) )?
  155         [ \t]* :
  156     )
  157 
  158 |   (?P<Attribute>
  159         ^
  160         (?P<AttributeIndent> [ \t]* )
  161         self [ \t]* \. [ \t]*
  162         (?P<AttributeName> \w+ )
  163         [ \t]* =
  164     )
  165 
  166 |   (?P<Variable>
  167         ^
  168         (?P<VariableIndent> [ \t]* )
  169         (?P<VariableName> \w+ )
  170         [ \t]* = [ \t]* (?P<VariableSignal> (?:pyqtSignal)? )
  171     )
  172 
  173 |   (?P<Main>
  174         ^
  175         if \s+ __name__ \s* == \s* [^:]+ : $
  176     )
  177 
  178 |   (?P<Import>
  179         ^ [ \t]* (?: import | from [ \t]+ \. [ \t]+ import ) [ \t]+
  180         (?P<ImportList> (?: [^#;\\\n]* (?: \\\n )* )* )
  181     )
  182 
  183 |   (?P<ImportFrom>
  184         ^ [ \t]* from [ \t]+
  185         (?P<ImportFromPath>
  186             \.* \w+
  187             (?:
  188                 [ \t]* \. [ \t]* \w+
  189             )*
  190         )
  191         [ \t]+
  192         import [ \t]+
  193         (?P<ImportFromList>
  194             (?: \( \s* .*? \s* \) )
  195             |
  196             (?: [^#;\\\n]* (?: \\\n )* )* )
  197     )
  198 
  199 |   (?P<ConditionalDefine>
  200         ^
  201         (?P<ConditionalDefineIndent> [ \t]* )
  202         (?: (?: if | elif ) [ \t]+ [^:]* | else [ \t]* ) :
  203         (?= \s* (?: async [ \t]+ )? def)
  204     )""",
  205     re.VERBOSE | re.DOTALL | re.MULTILINE).search
  206 
  207 _rb_getnext = re.compile(
  208     r"""
  209     (?P<Docstring>
  210         =begin [ \t]+ edoc (?P<DocstringContents> .*? ) =end
  211     )
  212     
  213 |   (?P<String>
  214         =begin .*? =end
  215 
  216     |   <<-? (?P<HereMarker1> [a-zA-Z0-9_]+? ) [ \t]* .*? (?P=HereMarker1)
  217 
  218     |   <<-? ['"] (?P<HereMarker2> .*? ) ['"] [ \t]* .*? (?P=HereMarker2)
  219 
  220     |   " [^"\\\n]* (?: \\. [^"\\\n]*)* "
  221 
  222     |   ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
  223     )
  224 
  225 |   (?P<Comment>
  226         ^
  227         [ \t]* \#+ .*? $
  228     )
  229 
  230 |   (?P<Method>
  231         ^
  232         (?P<MethodIndent> [ \t]* )
  233         def [ \t]+
  234         (?:
  235             (?P<MethodName2> [a-zA-Z0-9_]+ (?: \. | :: )
  236             [a-zA-Z_] [a-zA-Z0-9_?!=]* )
  237         |
  238             (?P<MethodName> [a-zA-Z_] [a-zA-Z0-9_?!=]* )
  239         |
  240             (?P<MethodName3> [^( \t]{1,3} )
  241         )
  242         [ \t]*
  243         (?:
  244             \( (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? ) \)
  245         )?
  246         [ \t]*
  247     )
  248 
  249 |   (?P<Class>
  250         ^
  251         (?P<ClassIndent> [ \t]* )
  252         class
  253         (?:
  254             [ \t]+
  255             (?P<ClassName> [A-Z] [a-zA-Z0-9_]* )
  256             [ \t]*
  257             (?P<ClassSupers> < [ \t]* [A-Z] [a-zA-Z0-9_]* )?
  258         |
  259             [ \t]* << [ \t]*
  260             (?P<ClassName2> [a-zA-Z_] [a-zA-Z0-9_]* )
  261         )
  262         [ \t]*
  263     )
  264 
  265 |   (?P<ClassIgnored>
  266         \(
  267         [ \t]*
  268         class
  269         .*?
  270         end
  271         [ \t]*
  272         \)
  273     )
  274 
  275 |   (?P<Module>
  276         ^
  277         (?P<ModuleIndent> [ \t]* )
  278         module [ \t]+
  279         (?P<ModuleName> [A-Z] [a-zA-Z0-9_]* )
  280         [ \t]*
  281     )
  282 
  283 |   (?P<AccessControl>
  284         ^
  285         (?P<AccessControlIndent> [ \t]* )
  286         (?:
  287             (?P<AccessControlType> private | public | protected ) [^_]
  288         |
  289             (?P<AccessControlType2>
  290             private_class_method | public_class_method )
  291         )
  292         \(?
  293         [ \t]*
  294         (?P<AccessControlList> (?: : [a-zA-Z0-9_]+ , \s* )*
  295         (?: : [a-zA-Z0-9_]+ )+ )?
  296         [ \t]*
  297         \)?
  298     )
  299 
  300 |   (?P<Attribute>
  301         ^
  302         (?P<AttributeIndent> [ \t]* )
  303         (?P<AttributeName> (?: @ | @@ | [A-Z]) [a-zA-Z0-9_]* )
  304         [ \t]* =
  305     )
  306 
  307 |   (?P<Attr>
  308         ^
  309         (?P<AttrIndent> [ \t]* )
  310         attr
  311         (?P<AttrType> (?: _accessor | _reader | _writer ) )?
  312         \(?
  313         [ \t]*
  314         (?P<AttrList> (?: : [a-zA-Z0-9_]+ , \s* )*
  315         (?: : [a-zA-Z0-9_]+ | true | false )+ )
  316         [ \t]*
  317         \)?
  318     )
  319 
  320 |   (?P<Begin>
  321             ^
  322             [ \t]*
  323             (?: if | unless | case | while | until | for | begin ) \b [^_]
  324         |
  325             [ \t]* do [ \t]* (?: \| .*? \| )? [ \t]* $
  326     )
  327 
  328 |   (?P<BeginEnd>
  329         \b (?: if ) \b [^_] .*? $
  330         |
  331         \b (?: if ) \b [^_] .*? end [ \t]* $
  332     )
  333 
  334 |   (?P<End>
  335         [ \t]*
  336         (?:
  337             end [ \t]* $
  338         |
  339             end \b [^_]
  340         )
  341     )""",
  342     re.VERBOSE | re.DOTALL | re.MULTILINE).search
  343 
  344 _hashsub = re.compile(r"""^([ \t]*)#[ \t]?""", re.MULTILINE).sub
  345 
  346 _commentsub = re.compile(r"""#[^\n]*\n|#[^\n]*$""").sub
  347 
  348 _modules = {}                           # cache of modules we've seen
  349 
  350 
  351 class VisibilityBase(object):
  352     """
  353     Class implementing the visibility aspect of all objects.
  354     """
  355     def isPrivate(self):
  356         """
  357         Public method to check, if the visibility is Private.
  358         
  359         @return flag indicating Private visibility (boolean)
  360         """
  361         return self.visibility == 0
  362         
  363     def isProtected(self):
  364         """
  365         Public method to check, if the visibility is Protected.
  366         
  367         @return flag indicating Protected visibility (boolean)
  368         """
  369         return self.visibility == 1
  370         
  371     def isPublic(self):
  372         """
  373         Public method to check, if the visibility is Public.
  374         
  375         @return flag indicating Public visibility (boolean)
  376         """
  377         return self.visibility == 2
  378         
  379     def setPrivate(self):
  380         """
  381         Public method to set the visibility to Private.
  382         """
  383         self.visibility = 0
  384         
  385     def setProtected(self):
  386         """
  387         Public method to set the visibility to Protected.
  388         """
  389         self.visibility = 1
  390         
  391     def setPublic(self):
  392         """
  393         Public method to set the visibility to Public.
  394         """
  395         self.visibility = 2
  396 
  397 
  398 class Module(object):
  399     """
  400     Class to represent a Python module.
  401     """
  402     def __init__(self, name, file=None, moduleType=None):
  403         """
  404         Constructor
  405         
  406         @param name name of this module (string)
  407         @param file filename of file containing this module (string)
  408         @param moduleType type of this module
  409         """
  410         self.name = name
  411         self.file = file
  412         self.modules = {}
  413         self.modules_counts = {}
  414         self.classes = {}
  415         self.classes_counts = {}
  416         self.functions = {}
  417         self.functions_counts = {}
  418         self.description = ""
  419         self.globals = {}
  420         self.imports = []
  421         self.from_imports = {}
  422         self.package = '.'.join(name.split('.')[:-1])
  423         self.type = moduleType
  424         if moduleType in [PY_SOURCE, PTL_SOURCE]:
  425             self._getnext = _py_getnext
  426         elif moduleType == RB_SOURCE:
  427             self._getnext = _rb_getnext
  428         else:
  429             self._getnext = None
  430     
  431     def addClass(self, name, _class):
  432         """
  433         Public method to add information about a class.
  434         
  435         @param name name of class to be added (string)
  436         @param _class Class object to be added
  437         """
  438         if name in self.classes:
  439             self.classes_counts[name] += 1
  440             name = "{0}_{1:d}".format(name, self.classes_counts[name])
  441         else:
  442             self.classes_counts[name] = 0
  443         self.classes[name] = _class
  444     
  445     def addModule(self, name, module):
  446         """
  447         Public method to add information about a Ruby module.
  448         
  449         @param name name of module to be added (string)
  450         @param module Module object to be added
  451         """
  452         if name in self.modules:
  453             self.modules_counts[name] += 1
  454             name = "{0}_{1:d}".format(name, self.modules_counts[name])
  455         else:
  456             self.modules_counts[name] = 0
  457         self.modules[name] = module
  458     
  459     def addFunction(self, name, function):
  460         """
  461         Public method to add information about a function.
  462         
  463         @param name name of function to be added (string)
  464         @param function Function object to be added
  465         """
  466         if name in self.functions:
  467             self.functions_counts[name] += 1
  468             name = "{0}_{1:d}".format(name, self.functions_counts[name])
  469         else:
  470             self.functions_counts[name] = 0
  471         self.functions[name] = function
  472     
  473     def addGlobal(self, name, attr):
  474         """
  475         Public method to add information about global variables.
  476         
  477         @param name name of the global to add (string)
  478         @param attr Attribute object to be added
  479         """
  480         if name not in self.globals:
  481             self.globals[name] = attr
  482         else:
  483             self.globals[name].addAssignment(attr.lineno)
  484     
  485     def addDescription(self, description):
  486         """
  487         Public method to store the modules docstring.
  488         
  489         @param description the docstring to be stored (string)
  490         """
  491         self.description = description
  492     
  493     def scan(self, src):
  494         """
  495         Public method to scan the source text and retrieve the relevant
  496         information.
  497         
  498         @param src the source text to be scanned (string)
  499         """
  500         # convert eol markers the Python style
  501         src = src.replace("\r\n", "\n").replace("\r", "\n")
  502         if self.type in [PY_SOURCE, PTL_SOURCE]:
  503             self.__py_scan(src)
  504         elif self.type == RB_SOURCE:
  505             self.__rb_scan(src)
  506     
  507     def __py_setVisibility(self, objectRef):
  508         """
  509         Private method to set the visibility of an object.
  510         
  511         @param objectRef reference to the object (Attribute, Class or Function)
  512         """
  513         if objectRef.name.startswith('__'):
  514             objectRef.setPrivate()
  515         elif objectRef.name.startswith('_'):
  516             objectRef.setProtected()
  517         else:
  518             objectRef.setPublic()
  519     
  520     def __py_scan(self, src):
  521         """
  522         Private method to scan the source text of a Python module and retrieve
  523         the relevant information.
  524         
  525         @param src the source text to be scanned (string)
  526         """
  527         lineno, last_lineno_pos = 1, 0
  528         lastGlobalEntry = None
  529         classstack = []  # stack of (class, indent) pairs
  530         conditionalsstack = []  # stack of indents of conditional defines
  531         deltastack = []
  532         deltaindent = 0
  533         deltaindentcalculated = 0
  534         i = 0
  535         modulelevel = True
  536         cur_obj = self
  537         modifierType = Function.General
  538         modifierIndent = -1
  539         while True:
  540             m = self._getnext(src, i)
  541             if not m:
  542                 break
  543             start, i = m.span()
  544             
  545             if m.start("MethodModifier") >= 0:
  546                 modifierIndent = _indent(m.group("MethodModifierIndent"))
  547                 modifierType = m.group("MethodModifierType")
  548             
  549             elif m.start("Method") >= 0:
  550                 # found a method definition or function
  551                 thisindent = _indent(m.group("MethodIndent"))
  552                 meth_name = m.group("MethodName")
  553                 meth_sig = m.group("MethodSignature")
  554                 meth_sig = meth_sig.replace('\\\n', '')
  555                 meth_ret = m.group("MethodReturnAnnotation")
  556                 meth_ret = meth_ret.replace('\\\n', '')
  557                 if m.group("MethodPyQtSignature") is not None:
  558                     meth_pyqtSig = (
  559                         m.group("MethodPyQtSignature")
  560                         .replace('\\\n', '')
  561                         .split('result')[0]
  562                         .split('name')[0]
  563                         .strip("\"', \t")
  564                     )
  565                 else:
  566                     meth_pyqtSig = None
  567                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  568                 last_lineno_pos = start
  569                 if modifierType and modifierIndent == thisindent:
  570                     if modifierType == "@staticmethod":
  571                         modifier = Function.Static
  572                     elif modifierType == "@classmethod":
  573                         modifier = Function.Class
  574                     else:
  575                         modifier = Function.General
  576                 else:
  577                     modifier = Function.General
  578                 # modify indentation level for conditional defines
  579                 if conditionalsstack:
  580                     if thisindent > conditionalsstack[-1]:
  581                         if not deltaindentcalculated:
  582                             deltastack.append(
  583                                 thisindent - conditionalsstack[-1])
  584                             deltaindent = reduce(
  585                                 lambda x, y: x + y, deltastack)
  586                             deltaindentcalculated = 1
  587                         thisindent -= deltaindent
  588                     else:
  589                         while (
  590                             conditionalsstack and
  591                             conditionalsstack[-1] >= thisindent
  592                         ):
  593                             del conditionalsstack[-1]
  594                             if deltastack:
  595                                 del deltastack[-1]
  596                         deltaindentcalculated = 0
  597                 # close all classes indented at least as much
  598                 while classstack and classstack[-1][1] >= thisindent:
  599                     if (
  600                         classstack[-1][0] is not None and
  601                         isinstance(classstack[-1][0], (Class, Function))
  602                     ):
  603                         # record the end line of this class or function
  604                         classstack[-1][0].setEndLine(lineno - 1)
  605                     del classstack[-1]
  606                 if classstack:
  607                     csi = -1
  608                     while csi >= -len(classstack):
  609                         # nested defs are added to the class
  610                         cur_class = classstack[csi][0]
  611                         csi -= 1
  612                         if cur_class is None:
  613                             continue
  614                         
  615                         if isinstance(cur_class, Class):
  616                             # it's a class method
  617                             f = Function(
  618                                 None, meth_name, None, lineno,
  619                                 meth_sig, meth_pyqtSig, modifierType=modifier,
  620                                 annotation=meth_ret)
  621                             self.__py_setVisibility(f)
  622                             cur_class.addMethod(meth_name, f)
  623                             break
  624                     else:
  625                         # it's a nested function of a module function
  626                         f = Function(
  627                             self.name, meth_name, self.file, lineno,
  628                             meth_sig, meth_pyqtSig, modifierType=modifier,
  629                             annotation=meth_ret)
  630                         self.__py_setVisibility(f)
  631                         self.addFunction(meth_name, f)
  632                 else:
  633                     # it's a module function
  634                     f = Function(self.name, meth_name, self.file, lineno,
  635                                  meth_sig, meth_pyqtSig, modifierType=modifier,
  636                                  annotation=meth_ret)
  637                     self.__py_setVisibility(f)
  638                     self.addFunction(meth_name, f)
  639                 if not classstack:
  640                     if lastGlobalEntry:
  641                         lastGlobalEntry.setEndLine(lineno - 1)
  642                     lastGlobalEntry = f
  643                 if cur_obj and isinstance(cur_obj, Function):
  644                     cur_obj.setEndLine(lineno - 1)
  645                 cur_obj = f
  646                 classstack.append((None, thisindent))  # Marker for nested fns
  647                 
  648                 # reset the modifier settings
  649                 modifierType = Function.General
  650                 modifierIndent = -1
  651             
  652             elif m.start("Docstring") >= 0:
  653                 contents = m.group("DocstringContents3")
  654                 if contents is not None:
  655                     contents = _hashsub(r"\1", contents)
  656                 else:
  657                     if self.file.lower().endswith('.ptl'):
  658                         contents = ""
  659                     else:
  660                         contents = (
  661                             m.group("DocstringContents1") or
  662                             m.group("DocstringContents2")
  663                         )
  664                 if cur_obj:
  665                     cur_obj.addDescription(contents)
  666             
  667             elif m.start("String") >= 0:
  668                 if (
  669                     modulelevel and (
  670                         src[start - len('\r\n'):start] == '\r\n' or
  671                         src[start - len('\n'):start] == '\n' or
  672                         src[start - len('\r'):start] == '\r'
  673                     )
  674                 ):
  675                     contents = m.group("StringContents3")
  676                     if contents is not None:
  677                         contents = _hashsub(r"\1", contents)
  678                     else:
  679                         if self.file.lower().endswith('.ptl'):
  680                             contents = ""
  681                         else:
  682                             contents = (
  683                                 m.group("StringContents1") or
  684                                 m.group("StringContents2")
  685                             )
  686                     if cur_obj:
  687                         cur_obj.addDescription(contents)
  688             
  689             elif m.start("Class") >= 0:
  690                 # we found a class definition
  691                 thisindent = _indent(m.group("ClassIndent"))
  692                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  693                 last_lineno_pos = start
  694                 # close all classes indented at least as much
  695                 while classstack and classstack[-1][1] >= thisindent:
  696                     if (
  697                         classstack[-1][0] is not None and
  698                         isinstance(classstack[-1][0], (Class, Function))
  699                     ):
  700                         # record the end line of this class or function
  701                         classstack[-1][0].setEndLine(lineno - 1)
  702                     del classstack[-1]
  703                 class_name = m.group("ClassName")
  704                 inherit = m.group("ClassSupers")
  705                 if inherit:
  706                     # the class inherits from other classes
  707                     inherit = inherit[1:-1].strip()
  708                     inherit = _commentsub('', inherit)
  709                     names = []
  710                     for n in inherit.split(','):
  711                         n = n.strip()
  712                         if n:
  713                             if n in self.classes:
  714                                 # we know this super class
  715                                 n = self.classes[n].name
  716                             else:
  717                                 c = n.split('.')
  718                                 if len(c) > 1:
  719                                     # super class is of the
  720                                     # form module.class:
  721                                     # look in module for class
  722                                     m = c[-2]
  723                                     c = c[-1]
  724                                     if m in _modules:
  725                                         m = _modules[m]
  726                                         n = m.name
  727                             names.append(n)
  728                     inherit = names
  729                 # remember this class
  730                 cur_class = Class(self.name, class_name, inherit,
  731                                   self.file, lineno)
  732                 self.__py_setVisibility(cur_class)
  733                 cur_obj = cur_class
  734                 # add nested classes to the module
  735                 self.addClass(class_name, cur_class)
  736                 if not classstack:
  737                     if lastGlobalEntry:
  738                         lastGlobalEntry.setEndLine(lineno - 1)
  739                     lastGlobalEntry = cur_class
  740                 classstack.append((cur_class, thisindent))
  741             
  742             elif m.start("Attribute") >= 0:
  743                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  744                 last_lineno_pos = start
  745                 index = -1
  746                 while index >= -len(classstack):
  747                     if classstack[index][0] is not None:
  748                         attrName = m.group("AttributeName")
  749                         attr = Attribute(
  750                             self.name, attrName, self.file, lineno)
  751                         self.__py_setVisibility(attr)
  752                         classstack[index][0].addAttribute(attrName, attr)
  753                         break
  754                     else:
  755                         index -= 1
  756             
  757             elif m.start("Main") >= 0:
  758                 # 'main' part of the script, reset class stack
  759                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  760                 last_lineno_pos = start
  761                 classstack = []
  762             
  763             elif m.start("Variable") >= 0:
  764                 thisindent = _indent(m.group("VariableIndent"))
  765                 variable_name = m.group("VariableName")
  766                 isSignal = m.group("VariableSignal") != ""
  767                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  768                 last_lineno_pos = start
  769                 if thisindent == 0:
  770                     # global variable
  771                     attr = Attribute(
  772                         self.name, variable_name, self.file, lineno,
  773                         isSignal=isSignal)
  774                     self.__py_setVisibility(attr)
  775                     self.addGlobal(variable_name, attr)
  776                     if lastGlobalEntry:
  777                         lastGlobalEntry.setEndLine(lineno - 1)
  778                     lastGlobalEntry = None
  779                 else:
  780                     index = -1
  781                     while index >= -len(classstack):
  782                         if classstack[index][1] >= thisindent:
  783                             index -= 1
  784                         else:
  785                             if (
  786                                 classstack[index][0] is not None and
  787                                 isinstance(classstack[index][0], Class)
  788                             ):
  789                                 attr = Attribute(
  790                                     self.name, variable_name, self.file,
  791                                     lineno, isSignal=isSignal)
  792                                 self.__py_setVisibility(attr)
  793                                 classstack[index][0].addGlobal(
  794                                     variable_name, attr)
  795                             break
  796 
  797             elif m.start("Import") >= 0:
  798                 ## import module
  799                 names = [n.strip() for n in
  800                          "".join(m.group("ImportList").splitlines())
  801                          .replace("\\", "").split(',')]
  802                 self.imports.extend(
  803                     [name for name in names
  804                      if name not in self.imports])
  805             
  806             elif m.start("ImportFrom") >= 0:
  807                 ## from module import stuff
  808                 mod = m.group("ImportFromPath")
  809                 namesLines = (m.group("ImportFromList")
  810                               .replace("(", "").replace(")", "")
  811                               .replace("\\", "")
  812                               .strip().splitlines())
  813                 namesLines = [line.split("#")[0].strip()
  814                               for line in namesLines]
  815                 names = [n.strip() for n in
  816                          "".join(namesLines)
  817                          .split(',')]
  818                 if mod not in self.from_imports:
  819                     self.from_imports[mod] = []
  820                 self.from_imports[mod].extend(
  821                     [name for name in names
  822                      if name not in self.from_imports[mod]])
  823             
  824             elif m.start("ConditionalDefine") >= 0:
  825                 # a conditional function/method definition
  826                 thisindent = _indent(m.group("ConditionalDefineIndent"))
  827                 while (
  828                     conditionalsstack and
  829                     conditionalsstack[-1] >= thisindent
  830                 ):
  831                     del conditionalsstack[-1]
  832                     if deltastack:
  833                         del deltastack[-1]
  834                 conditionalsstack.append(thisindent)
  835                 deltaindentcalculated = 0
  836             
  837             elif m.start("Comment") >= 0:
  838                 if modulelevel:
  839                     continue
  840             
  841             modulelevel = False
  842     
  843     def __rb_scan(self, src):
  844         """
  845         Private method to scan the source text of a Python module and retrieve
  846         the relevant information.
  847         
  848         @param src the source text to be scanned (string)
  849         """
  850         lineno, last_lineno_pos = 1, 0
  851         classstack = []  # stack of (class, indent) pairs
  852         acstack = []    # stack of (access control, indent) pairs
  853         indent = 0
  854         i = 0
  855         cur_obj = self
  856         lastGlobalEntry = None
  857         while True:
  858             m = self._getnext(src, i)
  859             if not m:
  860                 break
  861             start, i = m.span()
  862             
  863             if m.start("Method") >= 0:
  864                 # found a method definition or function
  865                 thisindent = indent
  866                 indent += 1
  867                 meth_name = (
  868                     m.group("MethodName") or
  869                     m.group("MethodName2") or
  870                     m.group("MethodName3")
  871                 )
  872                 meth_sig = m.group("MethodSignature")
  873                 meth_sig = meth_sig and meth_sig.replace('\\\n', '') or ''
  874                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  875                 last_lineno_pos = start
  876                 if meth_name.startswith('self.'):
  877                     meth_name = meth_name[5:]
  878                 elif meth_name.startswith('self::'):
  879                     meth_name = meth_name[6:]
  880                 # close all classes/modules indented at least as much
  881                 while classstack and classstack[-1][1] >= thisindent:
  882                     if (
  883                         classstack[-1][0] is not None and
  884                         isinstance(classstack[-1][0],
  885                                    (Class, Function, RbModule))
  886                     ):
  887                         # record the end line of this class, function or module
  888                         classstack[-1][0].setEndLine(lineno - 1)
  889                     del classstack[-1]
  890                 while acstack and acstack[-1][1] >= thisindent:
  891                     del acstack[-1]
  892                 if classstack:
  893                     csi = -1
  894                     while csi >= -len(classstack):
  895                         # nested defs are added to the class
  896                         cur_class = classstack[csi][0]
  897                         csi -= 1
  898                         if cur_class is None:
  899                             continue
  900                         
  901                         if (
  902                             isinstance(cur_class, Class) or
  903                             isinstance(cur_class, RbModule)
  904                         ):
  905                             # it's a class/module method
  906                             f = Function(None, meth_name,
  907                                          None, lineno, meth_sig)
  908                             cur_class.addMethod(meth_name, f)
  909                             break
  910                     else:
  911                         # it's a nested function of a module function
  912                         f = Function(
  913                             self.name, meth_name, self.file, lineno, meth_sig)
  914                         self.addFunction(meth_name, f)
  915                     # set access control
  916                     if acstack:
  917                         accesscontrol = acstack[-1][0]
  918                         if accesscontrol == "private":
  919                             f.setPrivate()
  920                         elif accesscontrol == "protected":
  921                             f.setProtected()
  922                         elif accesscontrol == "public":
  923                             f.setPublic()
  924                 else:
  925                     # it's a function
  926                     f = Function(
  927                         self.name, meth_name, self.file, lineno, meth_sig)
  928                     self.addFunction(meth_name, f)
  929                 if not classstack:
  930                     if lastGlobalEntry:
  931                         lastGlobalEntry.setEndLine(lineno - 1)
  932                     lastGlobalEntry = f
  933                 if cur_obj and isinstance(cur_obj, Function):
  934                     cur_obj.setEndLine(lineno - 1)
  935                 cur_obj = f
  936                 classstack.append((None, thisindent))  # Marker for nested fns
  937             
  938             elif m.start("Docstring") >= 0:
  939                 contents = m.group("DocstringContents")
  940                 if contents is not None:
  941                     contents = _hashsub(r"\1", contents)
  942                 if cur_obj:
  943                     cur_obj.addDescription(contents)
  944             
  945             elif m.start("String") >= 0:
  946                 pass
  947             
  948             elif m.start("Comment") >= 0:
  949                 pass
  950             
  951             elif m.start("ClassIgnored") >= 0:
  952                 pass
  953             
  954             elif m.start("Class") >= 0:
  955                 # we found a class definition
  956                 thisindent = indent
  957                 indent += 1
  958                 lineno = lineno + src.count('\n', last_lineno_pos, start)
  959                 last_lineno_pos = start
  960                 # close all classes/modules indented at least as much
  961                 while classstack and classstack[-1][1] >= thisindent:
  962                     if (
  963                         classstack[-1][0] is not None and
  964                         isinstance(classstack[-1][0],
  965                                    (Class, Function, RbModule))
  966                     ):
  967                         # record the end line of this class, function or module
  968                         classstack[-1][0].setEndLine(lineno - 1)
  969                     del classstack[-1]
  970                 class_name = m.group("ClassName") or m.group("ClassName2")
  971                 inherit = m.group("ClassSupers")
  972                 if inherit:
  973                     # the class inherits from other classes
  974                     inherit = inherit[1:].strip()
  975                     inherit = [_commentsub('', inherit)]
  976                 # remember this class
  977                 cur_class = Class(self.name, class_name, inherit,
  978                                   self.file, lineno)
  979                 # add nested classes to the file
  980                 if classstack and isinstance(classstack[-1][0], RbModule):
  981                     parent_obj = classstack[-1][0]
  982                 else:
  983                     parent_obj = self
  984                 if class_name in parent_obj.classes:
  985                     cur_class = parent_obj.classes[class_name]
  986                 elif (
  987                     classstack and
  988                     isinstance(classstack[-1][0], Class) and
  989                     class_name == "self"
  990                 ):
  991                     cur_class = classstack[-1][0]
  992                 else:
  993                     parent_obj.addClass(class_name, cur_class)
  994                 if not classstack:
  995                     if lastGlobalEntry:
  996                         lastGlobalEntry.setEndLine(lineno - 1)
  997                     lastGlobalEntry = cur_class
  998                 cur_obj = cur_class
  999                 classstack.append((cur_class, thisindent))
 1000                 while acstack and acstack[-1][1] >= thisindent:
 1001                     del acstack[-1]
 1002                 acstack.append(["public", thisindent])
 1003                 # default access control is 'public'
 1004             
 1005             elif m.start("Module") >= 0:
 1006                 # we found a module definition
 1007                 thisindent = indent
 1008                 indent += 1
 1009                 lineno = lineno + src.count('\n', last_lineno_pos, start)
 1010                 last_lineno_pos = start
 1011                 # close all classes/modules indented at least as much
 1012                 while classstack and classstack[-1][1] >= thisindent:
 1013                     if (
 1014                         classstack[-1][0] is not None and
 1015                         isinstance(classstack[-1][0],
 1016                                    (Class, Function, RbModule))
 1017                     ):
 1018                         # record the end line of this class, function or module
 1019                         classstack[-1][0].setEndLine(lineno - 1)
 1020                     del classstack[-1]
 1021                 module_name = m.group("ModuleName")
 1022                 # remember this class
 1023                 cur_class = RbModule(self.name, module_name,
 1024                                      self.file, lineno)
 1025                 # add nested Ruby modules to the file
 1026                 if module_name in self.modules:
 1027                     cur_class = self.modules[module_name]
 1028                 else:
 1029                     self.addModule(module_name, cur_class)
 1030                 if not classstack:
 1031                     if lastGlobalEntry:
 1032                         lastGlobalEntry.setEndLine(lineno - 1)
 1033                     lastGlobalEntry = cur_class
 1034                 cur_obj = cur_class
 1035                 classstack.append((cur_class, thisindent))
 1036                 while acstack and acstack[-1][1] >= thisindent:
 1037                     del acstack[-1]
 1038                 acstack.append(["public", thisindent])
 1039                 # default access control is 'public'
 1040             
 1041             elif m.start("AccessControl") >= 0:
 1042                 aclist = m.group("AccessControlList")
 1043                 if aclist is None:
 1044                     index = -1
 1045                     while index >= -len(acstack):
 1046                         if acstack[index][1] < indent:
 1047                             actype = (
 1048                                 m.group("AccessControlType") or
 1049                                 m.group("AccessControlType2").split('_')[0]
 1050                             )
 1051                             acstack[index][0] = actype.lower()
 1052                             break
 1053                         else:
 1054                             index -= 1
 1055                 else:
 1056                     index = -1
 1057                     while index >= -len(classstack):
 1058                         if (
 1059                             classstack[index][0] is not None and
 1060                             not isinstance(classstack[index][0], Function) and
 1061                             not classstack[index][1] >= indent
 1062                         ):
 1063                             parent = classstack[index][0]
 1064                             actype = (
 1065                                 m.group("AccessControlType") or
 1066                                 m.group("AccessControlType2").split('_')[0]
 1067                             )
 1068                             actype = actype.lower()
 1069                             for name in aclist.split(","):
 1070                                 # get rid of leading ':'
 1071                                 name = name.strip()[1:]
 1072                                 acmeth = parent.getMethod(name)
 1073                                 if acmeth is None:
 1074                                     continue
 1075                                 if actype == "private":
 1076                                     acmeth.setPrivate()
 1077                                 elif actype == "protected":
 1078                                     acmeth.setProtected()
 1079                                 elif actype == "public":
 1080                                     acmeth.setPublic()
 1081                             break
 1082                         else:
 1083                             index -= 1
 1084 
 1085             elif m.start("Attribute") >= 0:
 1086                 lineno = lineno + src.count('\n', last_lineno_pos, start)
 1087                 last_lineno_pos = start
 1088                 index = -1
 1089                 while index >= -len(classstack):
 1090                     if (
 1091                         classstack[index][0] is not None and
 1092                         not isinstance(classstack[index][0], Function) and
 1093                         not classstack[index][1] >= indent
 1094                     ):
 1095                         attrName = m.group("AttributeName")
 1096                         attr = Attribute(
 1097                             self.name, attrName, self.file, lineno)
 1098                         if attrName.startswith("@@") or attrName[0].isupper():
 1099                             classstack[index][0].addGlobal(attrName, attr)
 1100                         else:
 1101                             classstack[index][0].addAttribute(attrName, attr)
 1102                         break
 1103                     else:
 1104                         index -= 1
 1105                 else:
 1106                     attrName = m.group("AttributeName")
 1107                     if attrName[0] != "@":
 1108                         attr = Attribute(
 1109                             self.name, attrName, self.file, lineno)
 1110                         self.addGlobal(attrName, attr)
 1111                     if lastGlobalEntry:
 1112                         lastGlobalEntry.setEndLine(lineno - 1)
 1113                     lastGlobalEntry = None
 1114             
 1115             elif m.start("Attr") >= 0:
 1116                 lineno = lineno + src.count('\n', last_lineno_pos, start)
 1117                 last_lineno_pos = start
 1118                 index = -1
 1119                 while index >= -len(classstack):
 1120                     if (
 1121                         classstack[index][0] is not None and
 1122                         not isinstance(classstack[index][0], Function) and
 1123                         not classstack[index][1] >= indent
 1124                     ):
 1125                         parent = classstack[index][0]
 1126                         if m.group("AttrType") is None:
 1127                             nv = m.group("AttrList").split(",")
 1128                             if not nv:
 1129                                 break
 1130                             # get rid of leading ':'
 1131                             name = nv[0].strip()[1:]
 1132                             attr = (
 1133                                 parent.getAttribute("@" + name) or
 1134                                 parent.getAttribute("@@" + name) or
 1135                                 Attribute(
 1136                                     self.name, "@" + name, self.file, lineno)
 1137                             )
 1138                             if len(nv) == 1 or nv[1].strip() == "false":
 1139                                 attr.setProtected()
 1140                             elif nv[1].strip() == "true":
 1141                                 attr.setPublic()
 1142                             parent.addAttribute(attr.name, attr)
 1143                         else:
 1144                             access = m.group("AttrType")
 1145                             for name in m.group("AttrList").split(","):
 1146                                 # get rid of leading ':'
 1147                                 name = name.strip()[1:]
 1148                                 attr = (
 1149                                     parent.getAttribute("@" + name) or
 1150                                     parent.getAttribute("@@" + name) or
 1151                                     Attribute(
 1152                                         self.name, "@" + name, self.file,
 1153                                         lineno)
 1154                                 )
 1155                                 if access == "_accessor":
 1156                                     attr.setPublic()
 1157                                 elif (
 1158                                     access == "_reader" or
 1159                                     access == "_writer"
 1160                                 ):
 1161                                     if attr.isPrivate():
 1162                                         attr.setProtected()
 1163                                     elif attr.isProtected():
 1164                                         attr.setPublic()
 1165                                 parent.addAttribute(attr.name, attr)
 1166                         break
 1167                     else:
 1168                         index -= 1
 1169 
 1170             elif m.start("Begin") >= 0:
 1171                 # a begin of a block we are not interested in
 1172                 indent += 1
 1173             
 1174             elif m.start("End") >= 0:
 1175                 # an end of a block
 1176                 indent -= 1
 1177                 if indent < 0:
 1178                     # no negative indent allowed
 1179                     if classstack:
 1180                         # it's a class/module method
 1181                         indent = classstack[-1][1]
 1182                     else:
 1183                         indent = 0
 1184             
 1185             elif m.start("BeginEnd") >= 0:
 1186                 pass
 1187     
 1188     def createHierarchy(self):
 1189         """
 1190         Public method to build the inheritance hierarchy for all classes of
 1191         this module.
 1192         
 1193         @return A dictionary with inheritance hierarchies.
 1194         """
 1195         hierarchy = {}
 1196         for cls in list(list(self.classes.keys())):
 1197             self.assembleHierarchy(cls, self.classes, [cls], hierarchy)
 1198         for mod in list(list(self.modules.keys())):
 1199             self.assembleHierarchy(mod, self.modules, [mod], hierarchy)
 1200         return hierarchy
 1201     
 1202     def assembleHierarchy(self, name, classes, path, result):
 1203         """
 1204         Public method to assemble the inheritance hierarchy.
 1205         
 1206         This method will traverse the class hierarchy, from a given class
 1207         and build up a nested dictionary of super-classes. The result is
 1208         intended to be inverted, i.e. the highest level are the super classes.
 1209         
 1210         This code is borrowed from Boa Constructor.
 1211         
 1212         @param name name of class to assemble hierarchy (string)
 1213         @param classes A dictionary of classes to look in.
 1214         @param path
 1215         @param result The resultant hierarchy
 1216         """
 1217         rv = {}
 1218         if name in classes:
 1219             for cls in classes[name].super:
 1220                 if cls not in classes:
 1221                     rv[cls] = {}
 1222                     exhausted = path + [cls]
 1223                     exhausted.reverse()
 1224                     self.addPathToHierarchy(
 1225                         exhausted, result, self.addPathToHierarchy)
 1226                 else:
 1227                     rv[cls] = self.assembleHierarchy(
 1228                         cls, classes, path + [cls], result)
 1229         
 1230         if len(rv) == 0:
 1231             exhausted = path
 1232             exhausted.reverse()
 1233             self.addPathToHierarchy(exhausted, result, self.addPathToHierarchy)
 1234     
 1235     def addPathToHierarchy(self, path, result, fn):
 1236         """
 1237         Public method to put the exhausted path into the result dictionary.
 1238         
 1239         @param path the exhausted path of classes
 1240         @param result the result dictionary
 1241         @param fn function to call for classe that are already part of the
 1242             result dictionary
 1243         """
 1244         if path[0] in list(list(result.keys())):
 1245             if len(path) > 1:
 1246                 fn(path[1:], result[path[0]], fn)
 1247         else:
 1248             for part in path:
 1249                 result[part] = {}
 1250                 result = result[part]
 1251     
 1252     def getName(self):
 1253         """
 1254         Public method to retrieve the modules name.
 1255         
 1256         @return module name (string)
 1257         """
 1258         return self.name
 1259     
 1260     def getFileName(self):
 1261         """
 1262         Public method to retrieve the modules filename.
 1263         
 1264         @return module filename (string)
 1265         """
 1266         return self.file
 1267     
 1268     def getType(self):
 1269         """
 1270         Public method to get the type of the module's source.
 1271         
 1272         @return type of the modules's source (string)
 1273         """
 1274         if self.type in [PY_SOURCE, PTL_SOURCE]:
 1275             moduleType = "Python3"
 1276         elif self.type == RB_SOURCE:
 1277             moduleType = "Ruby"
 1278         else:
 1279             moduleType = ""
 1280         return moduleType
 1281 
 1282 
 1283 class Class(VisibilityBase):
 1284     """
 1285     Class to represent a Python class.
 1286     """
 1287     def __init__(self, module, name, superClasses, file, lineno):
 1288         """
 1289         Constructor
 1290         
 1291         @param module name of module containing this class (string)
 1292         @param name name of the class (string)
 1293         @param superClasses list of classnames this class is inherited from
 1294                 (list of strings)
 1295         @param file name of file containing this class (string)
 1296         @param lineno linenumber of the class definition (integer)
 1297         """
 1298         self.module = module
 1299         self.name = name
 1300         if superClasses is None:
 1301             superClasses = []
 1302         self.super = superClasses
 1303         self.methods = {}
 1304         self.attributes = {}
 1305         self.globals = {}
 1306         self.file = file
 1307         self.lineno = lineno
 1308         self.endlineno = -1     # marker for "not set"
 1309         self.description = ""
 1310         self.setPublic()
 1311 
 1312     def addMethod(self, name, function):
 1313         """
 1314         Public method to add information about a method.
 1315         
 1316         @param name name of method to be added (string)
 1317         @param function Function object to be added
 1318         """
 1319         self.methods[name] = function
 1320     
 1321     def getMethod(self, name):
 1322         """
 1323         Public method to retrieve a method by name.
 1324         
 1325         @param name name of the method (string)
 1326         @return the named method or None
 1327         """
 1328         try:
 1329             return self.methods[name]
 1330         except KeyError:
 1331             return None
 1332     
 1333     def addAttribute(self, name, attr):
 1334         """
 1335         Public method to add information about attributes.
 1336         
 1337         @param name name of the attribute to add (string)
 1338         @param attr Attribute object to be added
 1339         """
 1340         if name not in self.attributes:
 1341             self.attributes[name] = attr
 1342         else:
 1343             self.attributes[name].addAssignment(attr.lineno)
 1344     
 1345     def getAttribute(self, name):
 1346         """
 1347         Public method to retrieve an attribute by name.
 1348         
 1349         @param name name of the attribute (string)
 1350         @return the named attribute or None
 1351         """
 1352         try:
 1353             return self.attributes[name]
 1354         except KeyError:
 1355             return None
 1356     
 1357     def addGlobal(self, name, attr):
 1358         """
 1359         Public method to add information about global (class) variables.
 1360         
 1361         @param name name of the global to add (string)
 1362         @param attr Attribute object to be added
 1363         """
 1364         if name not in self.globals:
 1365             self.globals[name] = attr
 1366         else:
 1367             self.globals[name].addAssignment(attr.lineno)
 1368     
 1369     def addDescription(self, description):
 1370         """
 1371         Public method to store the class docstring.
 1372         
 1373         @param description the docstring to be stored (string)
 1374         """
 1375         self.description = description
 1376     
 1377     def setEndLine(self, endLineNo):
 1378         """
 1379         Public method to record the number of the last line of a class.
 1380         
 1381         @param endLineNo number of the last line (integer)
 1382         """
 1383         self.endlineno = endLineNo
 1384 
 1385 
 1386 class RbModule(Class):
 1387     """
 1388     Class to represent a Ruby module.
 1389     """
 1390     def __init__(self, module, name, file, lineno):
 1391         """
 1392         Constructor
 1393         
 1394         @param module name of module containing this class (string)
 1395         @param name name of the class (string)
 1396         @param file name of file containing this class (string)
 1397         @param lineno linenumber of the class definition (integer)
 1398         """
 1399         Class.__init__(self, module, name, None, file, lineno)
 1400         self.classes = {}
 1401     
 1402     def addClass(self, name, _class):
 1403         """
 1404         Public method to add information about a class.
 1405         
 1406         @param name name of class to be added (string)
 1407         @param _class Class object to be added
 1408         """
 1409         self.classes[name] = _class
 1410 
 1411 
 1412 class Function(VisibilityBase):
 1413     """
 1414     Class to represent a Python function or method.
 1415     """
 1416     General = 0
 1417     Static = 1
 1418     Class = 2
 1419     
 1420     def __init__(self, module, name, file, lineno, signature='',
 1421                  pyqtSignature=None, modifierType=General, annotation=""):
 1422         """
 1423         Constructor
 1424         
 1425         @param module name of module containing this function (string)
 1426         @param name name of the function (string)
 1427         @param file name of file containing this function (string)
 1428         @param lineno linenumber of the function definition (integer)
 1429         @param signature the functions call signature (string)
 1430         @param pyqtSignature the functions PyQt signature (string)
 1431         @param modifierType type of the function
 1432         @param annotation return annotation
 1433         """
 1434         self.module = module
 1435         self.name = name
 1436         self.file = file
 1437         self.lineno = lineno
 1438         self.endlineno = -1     # marker for "not set"
 1439         signature = _commentsub('', signature)
 1440         self.parameters = [e.strip() for e in signature.split(',')]
 1441         self.description = ""
 1442         self.pyqtSignature = pyqtSignature
 1443         self.modifier = modifierType
 1444         self.annotation = annotation
 1445         self.setPublic()
 1446     
 1447     def addDescription(self, description):
 1448         """
 1449         Public method to store the functions docstring.
 1450         
 1451         @param description the docstring to be stored (string)
 1452         """
 1453         self.description = description
 1454     
 1455     def setEndLine(self, endLineNo):
 1456         """
 1457         Public method to record the number of the last line of a class.
 1458         
 1459         @param endLineNo number of the last line (integer)
 1460         """
 1461         self.endlineno = endLineNo
 1462 
 1463 
 1464 class Attribute(VisibilityBase):
 1465     """
 1466     Class to represent a Python function or method.
 1467     """
 1468     def __init__(self, module, name, file, lineno, isSignal=False):
 1469         """
 1470         Constructor
 1471         
 1472         @param module name of module containing this function (string)
 1473         @param name name of the function (string)
 1474         @param file name of file containing this function (string)
 1475         @param lineno linenumber of the first attribute assignment (integer)
 1476         @keyparam isSignal flag indicating a signal definition (boolean)
 1477         """
 1478         self.module = module
 1479         self.name = name
 1480         self.file = file
 1481         self.lineno = lineno
 1482         self.isSignal = isSignal
 1483         self.setPublic()
 1484         self.linenos = [lineno]
 1485     
 1486     def addAssignment(self, lineno):
 1487         """
 1488         Public method to add another assignment line number.
 1489         
 1490         @param lineno linenumber of the additional attribute assignment
 1491             (integer)
 1492         """
 1493         if lineno not in self.linenos:
 1494             self.linenos.append(lineno)
 1495 
 1496 
 1497 def readModule(module, path=None, inpackage=False, basename="",
 1498                extensions=None, caching=True, ignoreBuiltinModules=False):
 1499     """
 1500     Function to read a module file and parse it.
 1501 
 1502     The module is searched in path and sys.path, read and parsed.
 1503     If the module was parsed before, the information is taken
 1504     from a cache in order to speed up processing.
 1505     
 1506     @param module name of the module to be parsed (string)
 1507     @param path search path for the module (list of strings)
 1508     @param inpackage flag indicating that module is inside a
 1509         package (boolean)
 1510     @param basename a path basename that is deleted from the filename of
 1511         the module file to be read (string)
 1512     @param extensions list of extensions, which should be considered valid
 1513         source file extensions (list of strings)
 1514     @param caching flag indicating that the parsed module should be
 1515         cached (boolean)
 1516     @param ignoreBuiltinModules flag indicating to ignore the builtin modules
 1517         (boolean)
 1518     @return reference to a Module object containing the parsed
 1519         module information (Module)
 1520     """
 1521     global _modules
 1522     
 1523     if extensions is None:
 1524         _extensions = ['.py', '.pyw', '.ptl', '.rb']
 1525     else:
 1526         _extensions = extensions[:]
 1527     try:
 1528         _extensions.remove('.py')
 1529     except ValueError:
 1530         pass
 1531     
 1532     modname = module
 1533     
 1534     if os.path.exists(module):
 1535         path = [os.path.dirname(module)]
 1536         if module.lower().endswith(".py"):
 1537             module = module[:-3]
 1538         if (
 1539             os.path.exists(os.path.join(path[0], "__init__.py")) or
 1540             os.path.exists(os.path.join(path[0], "__init__.rb")) or
 1541             inpackage
 1542         ):
 1543             if basename:
 1544                 module = module.replace(basename, "")
 1545             if os.path.isabs(module):
 1546                 modname = os.path.splitdrive(module)[1][len(os.sep):]
 1547             else:
 1548                 modname = module
 1549             modname = modname.replace(os.sep, '.')
 1550             inpackage = 1
 1551         else:
 1552             modname = os.path.basename(module)
 1553         for ext in _extensions:
 1554             if modname.lower().endswith(ext):
 1555                 modname = modname[:-len(ext)]
 1556                 break
 1557         module = os.path.basename(module)
 1558     
 1559     if caching and modname in _modules:
 1560         # we've seen this module before...
 1561         return _modules[modname]
 1562     
 1563     if not ignoreBuiltinModules and module in sys.builtin_module_names:
 1564         # this is a built-in module
 1565         mod = Module(modname, None, None)
 1566         if caching:
 1567             _modules[modname] = mod
 1568         return mod
 1569     
 1570     # search the path for the module
 1571     path = [] if path is None else path[:]
 1572     f = None
 1573     if inpackage:
 1574         try:
 1575             f, file, (suff, mode, moduleType) = find_module(
 1576                 module, path, _extensions)
 1577         except ImportError:
 1578             f = None
 1579     if f is None:
 1580         fullpath = path[:] + sys.path[:]
 1581         f, file, (suff, mode, moduleType) = find_module(
 1582             module, fullpath, _extensions)
 1583     if f:
 1584         f.close()
 1585     if moduleType not in SUPPORTED_TYPES:
 1586         # not supported source, can't do anything with this module
 1587         _modules[modname] = Module(modname, None, None)
 1588         return _modules[modname]
 1589     
 1590     mod = Module(modname, file, moduleType)
 1591     try:
 1592         src = Utilities.readEncodedFile(file)[0]
 1593         mod.scan(src)
 1594     except (UnicodeError, IOError):
 1595         pass
 1596     if caching:
 1597         _modules[modname] = mod
 1598     return mod
 1599 
 1600 
 1601 def _indent(ws):
 1602     """
 1603     Protected function to determine the indent width of a whitespace string.
 1604     
 1605     @param ws The whitespace string to be cheked. (string)
 1606     @return Length of the whitespace string after tab expansion.
 1607     """
 1608     return len(ws.expandtabs(TABWIDTH))
 1609 
 1610 
 1611 def find_module(name, path, extensions):
 1612     """
 1613     Module function to extend the Python module finding mechanism.
 1614     
 1615     This function searches for files in the given path. If the filename
 1616     doesn't have an extension or an extension of .py, the normal search
 1617     implemented in the imp module is used. For all other supported files
 1618     only path is searched.
 1619     
 1620     @param name filename or modulename to search for (string)
 1621     @param path search path (list of strings)
 1622     @param extensions list of extensions, which should be considered valid
 1623         source file extensions (list of strings)
 1624     @return tuple of the open file, pathname and description. Description
 1625         is a tuple of file suffix, file mode and file type)
 1626     @exception ImportError The file or module wasn't found.
 1627     """
 1628     for ext in extensions:
 1629         if name.lower().endswith(ext):
 1630             for p in path:      # only search in path
 1631                 if os.path.exists(os.path.join(p, name)):
 1632                     pathname = os.path.join(p, name)
 1633                     if ext == '.ptl':
 1634                         # Quixote page template
 1635                         return (open(pathname), pathname,
 1636                                 ('.ptl', 'r', PTL_SOURCE))
 1637                     elif ext == '.rb':
 1638                         # Ruby source file
 1639                         return (open(pathname), pathname,
 1640                                 ('.rb', 'r', RB_SOURCE))
 1641                     else:
 1642                         return (open(pathname), pathname,
 1643                                 (ext, 'r', PY_SOURCE))
 1644             raise ImportError
 1645     
 1646     # standard Python module file
 1647     if name.lower().endswith('.py'):
 1648         name = name[:-3]
 1649     
 1650     spec = importlib.machinery.PathFinder.find_spec(name, path)
 1651     if spec is None:
 1652         raise ImportError
 1653     if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
 1654         ext = os.path.splitext(spec.origin)[-1]
 1655         return (open(spec.origin), spec.origin, (ext, 'r', PY_SOURCE))
 1656     
 1657     raise ImportError
 1658 
 1659 
 1660 def resetParsedModules():
 1661     """
 1662     Module function to reset the list of modules already parsed.
 1663     """
 1664     _modules.clear()
 1665     
 1666 
 1667 def resetParsedModule(module, basename=""):
 1668     """
 1669     Module function to clear one module from the list of parsed modules.
 1670     
 1671     @param module Name of the module to be parsed (string)
 1672     @param basename a path basename. This basename is deleted from
 1673         the filename of the module file to be cleared. (string)
 1674     """
 1675     modname = module
 1676     
 1677     if os.path.exists(module):
 1678         path = [os.path.dirname(module)]
 1679         if module.lower().endswith(".py"):
 1680             module = module[:-3]
 1681         if os.path.exists(os.path.join(path[0], "__init__.py")):
 1682             if basename:
 1683                 module = module.replace(basename, "")
 1684             modname = module.replace(os.sep, '.')
 1685         else:
 1686             modname = os.path.basename(module)
 1687         if (
 1688             modname.lower().endswith(".ptl") or
 1689             modname.lower().endswith(".pyw")
 1690         ):
 1691             modname = modname[:-4]
 1692         elif modname.lower().endswith(".rb"):
 1693             modname = modname[:-3]
 1694         module = os.path.basename(module)
 1695     
 1696     if modname in _modules:
 1697         del _modules[modname]