Added support for class attributes, class methods and static methods to the class browsers and the source documentor.

Sat, 20 Aug 2011 16:28:25 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 20 Aug 2011 16:28:25 +0200
changeset 1227
c5db073a124f
parent 1225
254ec677c775
child 1228
7afaf2fca55b

Added support for class attributes, class methods and static methods to the class browsers and the source documentor.

DocumentationTools/ModuleDocumentor.py file | annotate | diff | comparison | revisions
DocumentationTools/TemplatesListsStyle.py file | annotate | diff | comparison | revisions
DocumentationTools/TemplatesListsStyleCSS.py file | annotate | diff | comparison | revisions
UI/BrowserModel.py file | annotate | diff | comparison | revisions
Utilities/ClassBrowsers/ClbrBaseClasses.py file | annotate | diff | comparison | revisions
Utilities/ClassBrowsers/pyclbr.py file | annotate | diff | comparison | revisions
Utilities/ModuleParser.py file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
icons/default/attribute_class.png file | annotate | diff | comparison | revisions
icons/default/attributes_class.png file | annotate | diff | comparison | revisions
icons/default/method_class.png file | annotate | diff | comparison | revisions
icons/default/method_static.png file | annotate | diff | comparison | revisions
--- a/DocumentationTools/ModuleDocumentor.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/DocumentationTools/ModuleDocumentor.py	Sat Aug 20 16:28:25 2011 +0200
@@ -18,7 +18,7 @@
 from . import TemplatesListsStyleCSS
 
 from Utilities import html_uencode
-from Utilities.ModuleParser import RB_SOURCE
+from Utilities.ModuleParser import RB_SOURCE, Function
 
 _signal = re.compile(r"""
     ^@signal [ \t]+
@@ -376,7 +376,12 @@
                 supers = 'None'
             
             globalsList = self.__genGlobalsListSection(_class)
-            methList, methBodies = self.__genMethodSection(_class, className)
+            classMethList, classMethBodies = \
+                self.__genMethodSection(_class, className, Function.Class)
+            methList, methBodies = \
+                self.__genMethodSection(_class, className, Function.General)
+            staticMethList, staticMethBodies = \
+                self.__genMethodSection(_class, className, Function.Static)
             
             try:
                 clsBody = self.classTemplate.format(**{ \
@@ -385,8 +390,10 @@
                     'ClassSuper': supers,
                     'ClassDescription': self.__formatDescription(_class.description),
                     'GlobalsList': globalsList,
+                    'ClassMethodList': classMethList,
                     'MethodList': methList,
-                    'MethodDetails': methBodies,
+                    'StaticMethodList': staticMethList,
+                    'MethodDetails': classMethBodies + methBodies + staticMethBodies,
                 })
             except TagError as e:
                 sys.stderr.write("Error in tags of description of class {0}.\n".format(
@@ -398,30 +405,33 @@
             
         return ''.join(classes)
         
-    def __genMethodsListSection(self, names, dict, className, clsName):
+    def __genMethodsListSection(self, names, dict, className, clsName,
+                                includeInit=True):
         """
         Private method to generate the methods list section of a class.
         
-        @param names The names to appear in the list. (list of strings)
-        @param dict A dictionary containing all relevant information.
-        @param className The class name containing the names.
-        @param clsName The visible class name containing the names.
-        @return The list section. (string)
+        @param names names to appear in the list (list of strings)
+        @param dict dictionary containing all relevant information
+        @param className class name containing the names
+        @param clsName visible class name containing the names
+        @param includeInit flag indicating to include the __init__ method (boolean)
+        @return methods list section (string)
         """
         lst = []
-        try:
-            lst.append(self.listEntryTemplate.format(**{ \
-                'Link': "{0}.{1}".format(className, '__init__'),
-                'Name': clsName,
-                'Description': self.__getShortDescription(dict['__init__'].description),
-                'Deprecated': self.__checkDeprecated(dict['__init__'].description) and \
-                               self.listEntryDeprecatedTemplate or "",
-            }))
-            self.keywords.append(("{0} (Constructor)".format(className),
-                                  "#{0}.{1}".format(className, '__init__')))
-        except KeyError:
-            pass
-            
+        if includeInit:
+            try:
+                lst.append(self.listEntryTemplate.format(**{ \
+                    'Link': "{0}.{1}".format(className, '__init__'),
+                    'Name': clsName,
+                    'Description': self.__getShortDescription(dict['__init__'].description),
+                    'Deprecated': self.__checkDeprecated(dict['__init__'].description) and \
+                                   self.listEntryDeprecatedTemplate or "",
+                }))
+                self.keywords.append(("{0} (Constructor)".format(className),
+                                      "#{0}.{1}".format(className, '__init__')))
+            except KeyError:
+                pass
+        
         for name in names:
             lst.append(self.listEntryTemplate.format(**{ \
                 'Link': "{0}.{1}".format(className, name),
@@ -434,17 +444,19 @@
                                   "#{0}.{1}".format(className, name)))
         return ''.join(lst)
         
-    def __genMethodSection(self, obj, className):
+    def __genMethodSection(self, obj, className, filter):
         """
         Private method to generate the method details section.
         
-        @param obj Reference to the object being formatted.
-        @param className Name of the class containing the method. (string)
-        @return The method list and method details section. (tuple of two string)
+        @param obj reference to the object being formatted
+        @param className name of the class containing the method (string)
+        @param filter filter value designating the method types
+        @return method list and method details section (tuple of two string)
         """
         methList = []
         methBodies = []
-        methods = sorted(list(obj.methods.keys()))
+        methods = sorted([k for k in obj.methods.keys()
+                          if obj.methods[k].modifier == filter])
         if '__init__' in methods:
             methods.remove('__init__')
             try:
@@ -463,13 +475,20 @@
                 sys.stderr.write("{0}\n".format(e))
                 methBody = ""
             methBodies.append(methBody)
-            
+        
+        if filter == Function.Class:
+            methodClassifier = " (class method)"
+        elif filter == Function.Static:
+            methodClassifier = " (static)"
+        else:
+            methodClassifier = ""
         for method in methods:
             try:
                 methBody = self.methodTemplate.format(**{ \
                     'Anchor': className,
                     'Class': obj.name,
                     'Method': obj.methods[method].name,
+                    'MethodClassifier': methodClassifier,
                     'MethodDescription': \
                         self.__formatDescription(obj.methods[method].description),
                     'Params': ', '.join(obj.methods[method].parameters[1:]),
@@ -482,7 +501,8 @@
                 methBody = ""
             methBodies.append(methBody)
             
-        methList = self.__genMethodsListSection(methods, obj.methods, className, obj.name)
+        methList = self.__genMethodsListSection(methods, obj.methods, className,
+            obj.name, includeInit='__init__' in methods)
         
         if not methList:
             methList = self.listEntryNoneTemplate
@@ -501,7 +521,8 @@
         for rbModuleName in rbModulesNames:
             rbModule = self.module.modules[rbModuleName]
             globalsList = self.__genGlobalsListSection(rbModule)
-            methList, methBodies = self.__genMethodSection(rbModule, rbModuleName)
+            methList, methBodies = \
+                self.__genMethodSection(rbModule, rbModuleName, Function.General)
             classList, classBodies = \
                 self.__genRbModulesClassesSection(rbModule, rbModuleName)
             
@@ -545,7 +566,8 @@
             else:
                 supers = 'None'
             
-            methList, methBodies = self.__genMethodSection(_class, className)
+            methList, methBodies = \
+                self.__genMethodSection(_class, className, Function.General)
             
             try:
                 clsBody = self.rbModulesClassTemplate.format(**{ \
--- a/DocumentationTools/TemplatesListsStyle.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/DocumentationTools/TemplatesListsStyle.py	Sat Aug 20 16:28:25 2011 +0200
@@ -62,15 +62,19 @@
 {{ClassSuper}}
 <h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">Class Attributes</h3>
 {{GlobalsList}}
+<h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">Class Methods</h3>
+{{ClassMethodList}}
 <h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">Methods</h3>
 {{MethodList}}
+<h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">Static Methods</h3>
+{{StaticMethodList}}
 {{MethodDetails}}
 <div align="right"><a style="color:{LinkColor}" href="#top">Up</a></div>
 <hr />'''
 
 methodTemplate = \
 '''<a NAME="{{Anchor}}.{{Method}}" ID="{{Anchor}}.{{Method}}"></a>
-<h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">{{Class}}.{{Method}}</h3>
+<h3 style="background-color:{Level2HeaderBgColor};color:{Level2HeaderColor}">{{Class}}.{{Method}}{{MethodClassifier}}</h3>
 <b>{{Method}}</b>(<i>{{Params}}</i>)
 {{MethodDescription}}'''
 
--- a/DocumentationTools/TemplatesListsStyleCSS.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/DocumentationTools/TemplatesListsStyleCSS.py	Sat Aug 20 16:28:25 2011 +0200
@@ -65,15 +65,19 @@
 {ClassSuper}
 <h3>Class Attributes</h3>
 {GlobalsList}
+<h3>Class Methods</h3>
+{ClassMethodList}
 <h3>Methods</h3>
 {MethodList}
+<h3>Static Methods</h3>
+{StaticMethodList}
 {MethodDetails}
 <div align="right"><a href="#top">Up</a></div>
 <hr />'''
 
 methodTemplate = \
 '''<a NAME="{Anchor}.{Method}" ID="{Anchor}.{Method}"></a>
-<h4>{Class}.{Method}</h4>
+<h4>{Class}.{Method}{MethodClassifier}</h4>
 <b>{Method}</b>(<i>{Params}</i>)
 {MethodDescription}'''
 
@@ -177,7 +181,7 @@
 </dd>'''
 
 signalsListTemplate = \
-'''<h4>Signals</h4>
+'''<h3>Signals</h3>
 <dl>
 {Signals}
 </dl>'''
@@ -189,7 +193,7 @@
 </dd>'''
 
 eventsListTemplate = \
-'''<h4>Events</h4>
+'''<h3>Events</h3>
 <dl>
 {Events}
 </dl>'''
--- a/UI/BrowserModel.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/UI/BrowserModel.py	Sat Aug 20 16:28:25 2011 +0200
@@ -612,7 +612,8 @@
         if len(cl.globals):
             node = BrowserClassAttributesItem(
                 parentItem, cl.globals,
-                QApplication.translate("BrowserModel", "Attributes (global)"))
+                QApplication.translate("BrowserModel", "Class Attributes"),
+                True)
             if repopulate:
                 self.addItem(node,
                     self.createIndex(parentItem.row(), 0, parentItem))
@@ -659,6 +660,7 @@
         @param parentItem reference to the class attributes item to be populated
         @param repopulate flag indicating a repopulation (boolean)
         """
+        classAttributes = parentItem.isClassAttributes()
         attributes = parentItem.attributes()
         if not attributes:
             return
@@ -669,7 +671,8 @@
                 self.beginInsertRows(self.createIndex(parentItem.row(), 0, parentItem),
                     0, len(keys) - 1)
             for key in keys:
-                node = BrowserClassAttributeItem(parentItem, attributes[key])
+                node = BrowserClassAttributeItem(parentItem, attributes[key],
+                                                 classAttributes)
                 self._addItem(node, parentItem)
             if repopulate:
                 self.endInsertRows()
@@ -1328,7 +1331,13 @@
         self.name = name
         self._functionObject = fn
         self._filename = filename
-        if self._functionObject.isPrivate():
+        if self._functionObject.modifier == \
+           Utilities.ClassBrowsers.ClbrBaseClasses.Function.Static:
+            self.icon = UI.PixmapCache.getIcon("method_static.png")
+        elif self._functionObject.modifier == \
+           Utilities.ClassBrowsers.ClbrBaseClasses.Function.Class:
+            self.icon = UI.PixmapCache.getIcon("method_class.png")
+        elif self._functionObject.isPrivate():
             self.icon = UI.PixmapCache.getIcon("method_private.png")
         elif self._functionObject.isProtected():
             self.icon = UI.PixmapCache.getIcon("method_protected.png")
@@ -1406,13 +1415,14 @@
     """
     Class implementing the data structure for browser class attributes items.
     """
-    def __init__(self, parent, attributes, text):
+    def __init__(self, parent, attributes, text, isClass=False):
         """
         Constructor
         
         @param parent parent item
         @param attributes list of attributes
         @param text text to be shown by this item (string)
+        @param isClass flag indicating class attributes (boolean)
         """
         BrowserItem.__init__(self, parent, text)
         
@@ -1420,7 +1430,11 @@
         self._attributes = attributes.copy()
         self._populated = False
         self._lazyPopulation = True
-        self.icon = UI.PixmapCache.getIcon("attributes.png")
+        if isClass:
+            self.icon = UI.PixmapCache.getIcon("attributes_class.png")
+        else:
+            self.icon = UI.PixmapCache.getIcon("attributes.png")
+        self.__isClass = isClass
     
     def attributes(self):
         """
@@ -1430,6 +1444,14 @@
         """
         return self._attributes
     
+    def isClassAttributes(self):
+        """
+        Public method returning the attributes type.
+        
+        @return flag indicating class attributes (boolean)
+        """
+        return self.__isClass
+    
     def lessThan(self, other, column, order):
         """
         Public method to check, if the item is less than the other one.
@@ -1452,19 +1474,22 @@
     """
     Class implementing the data structure for browser class attribute items.
     """
-    def __init__(self, parent, attribute):
+    def __init__(self, parent, attribute, isClass=False):
         """
         Constructor
         
         @param parent parent item
         @param attribute reference to the attribute object
+        @param isClass flag indicating a class attribute (boolean)
         """
         BrowserItem.__init__(self, parent, attribute.name)
         
         self.type_ = BrowserItemAttribute
         self._attributeObject = attribute
         self.__public = attribute.isPublic()
-        if attribute.isPrivate():
+        if isClass:
+            self.icon = UI.PixmapCache.getIcon("attribute_class.png")
+        elif attribute.isPrivate():
             self.icon = UI.PixmapCache.getIcon("attribute_private.png")
         elif attribute.isProtected():
             self.icon = UI.PixmapCache.getIcon("attribute_protected.png")
--- a/Utilities/ClassBrowsers/ClbrBaseClasses.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/Utilities/ClassBrowsers/ClbrBaseClasses.py	Sat Aug 20 16:28:25 2011 +0200
@@ -222,7 +222,12 @@
     """
     Class to represent a function or method.
     """
-    def __init__(self, module, name, file, lineno, signature='', separator=','):
+    General = 0
+    Static = 1
+    Class = 2
+    
+    def __init__(self, module, name, file, lineno, signature='', separator=',',
+                 modifierType=General):
         """
         Constructor
         
@@ -232,9 +237,11 @@
         @param lineno linenumber of the class definition
         @param signature parameterlist of the method
         @param separator string separating the parameters
+        @param modifierType type of the function
         """
         ClbrBase.__init__(self, module, name, file, lineno)
         self.parameters = [e.strip() for e in signature.split(separator)]
+        self.modifier = modifierType
 
 
 class Coding(ClbrBase):
--- a/Utilities/ClassBrowsers/pyclbr.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/Utilities/ClassBrowsers/pyclbr.py	Sat Aug 20 16:28:25 2011 +0200
@@ -52,6 +52,12 @@
         \]
     )
 
+|   (?P<MethodModifier>
+        ^
+        (?P<MethodModifierIndent> [ \t]* )
+        (?P<MethodModifierType> @classmethod | @staticmethod )
+    )
+
 |   (?P<Method>
         ^
         (?P<MethodIndent> [ \t]* )
@@ -142,7 +148,8 @@
     """
     Class to represent a Python function.
     """
-    def __init__(self, module, name, file, lineno, signature='', separator=','):
+    def __init__(self, module, name, file, lineno, signature='', separator=',',
+                 modifierType=ClbrBaseClasses.Function.General):
         """
         Constructor
         
@@ -152,9 +159,10 @@
         @param lineno linenumber of the class definition
         @param signature parameterlist of the method
         @param separator string separating the parameters
+        @param modifierType type of the function
         """
         ClbrBaseClasses.Function.__init__(self, module, name, file, lineno,
-                                          signature, separator)
+                                          signature, separator, modifierType)
         VisibilityMixin.__init__(self)
 
 
@@ -264,13 +272,18 @@
 
     lineno, last_lineno_pos = 1, 0
     i = 0
+    modifierType = ClbrBaseClasses.Function.General
+    modifierIndent = -1
     while True:
         m = _getnext(src, i)
         if not m:
             break
         start, i = m.span()
 
-        if m.start("Method") >= 0:
+        if m.start("MethodModifier") >= 0:
+            modifierIndent = _indent(m.group("MethodModifierIndent"))
+            modifierType = m.group("MethodModifierType")
+        elif m.start("Method") >= 0:
             # found a method definition or function
             thisindent = _indent(m.group("MethodIndent"))
             meth_name = m.group("MethodName")
@@ -279,6 +292,15 @@
             meth_sig = _commentsub('', meth_sig)
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
+            if modifierType and modifierIndent == thisindent:
+                if modifierType == "@staticmethod":
+                    modifier = ClbrBaseClasses.Function.Static
+                elif modifierType == "@classmethod":
+                    modifier = ClbrBaseClasses.Function.Class
+                else:
+                    modifier = ClbrBaseClasses.Function.General
+            else:
+                modifier = ClbrBaseClasses.Function.General
             # modify indentation level for conditional defines
             if conditionalsstack:
                 if thisindent > conditionalsstack[-1]:
@@ -304,12 +326,12 @@
                 if cur_class:
                     # it's a method/nested def
                     f = Function(None, meth_name,
-                                 file, lineno, meth_sig)
+                                 file, lineno, meth_sig, modifierType=modifier)
                     cur_class._addmethod(meth_name, f)
             else:
                 # it's a function
                 f = Function(module, meth_name,
-                             file, lineno, meth_sig)
+                             file, lineno, meth_sig, modifierType=modifier)
                 if meth_name in dict_counts:
                     dict_counts[meth_name] += 1
                     meth_name = "{0}_{1:d}".format(meth_name, dict_counts[meth_name])
@@ -317,6 +339,10 @@
                     dict_counts[meth_name] = 0
                 dict[meth_name] = f
             classstack.append((f, thisindent))  # Marker for nested fns
+            
+            # reset the modifier settings
+            modifierType = ClbrBaseClasses.Function.General
+            modifierIndent = -1
 
         elif m.start("String") >= 0:
             pass
--- a/Utilities/ModuleParser.py	Sat Aug 20 10:49:36 2011 +0200
+++ b/Utilities/ModuleParser.py	Sat Aug 20 16:28:25 2011 +0200
@@ -94,6 +94,12 @@
         \#\#\#
     )
 
+|   (?P<MethodModifier>
+        ^
+        (?P<MethodModifierIndent> [ \t]* )
+        (?P<MethodModifierType> @classmethod | @staticmethod )
+    )
+
 |   (?P<Method>
         (^ [ \t]* @ (?: PyQt4 \. )? (?: QtCore \. )? (?: pyqtSignature | pyqtSlot )
             [ \t]* \(
@@ -479,13 +485,18 @@
         i = 0
         modulelevel = 1
         cur_obj = self
+        modifierType = Function.General
+        modifierIndent = -1
         while True:
             m = self._getnext(src, i)
             if not m:
                 break
             start, i = m.span()
             
-            if m.start("Method") >= 0:
+            if m.start("MethodModifier") >= 0:
+                modifierIndent = _indent(m.group("MethodModifierIndent"))
+                modifierType = m.group("MethodModifierType")
+            elif m.start("Method") >= 0:
                 # found a method definition or function
                 thisindent = _indent(m.group("MethodIndent"))
                 meth_name = m.group("MethodName")
@@ -501,6 +512,15 @@
                     meth_pyqtSig = None
                 lineno = lineno + src.count('\n', last_lineno_pos, start)
                 last_lineno_pos = start
+                if modifierType and modifierIndent == thisindent:
+                    if modifierType == "@staticmethod":
+                        modifier = Function.Static
+                    elif modifierType == "@classmethod":
+                        modifier = Function.Class
+                    else:
+                        modifier = Function.General
+                else:
+                    modifier = Function.General
                 # modify indentation level for conditional defines
                 if conditionalsstack:
                     if thisindent > conditionalsstack[-1]:
@@ -538,24 +558,28 @@
                         if isinstance(cur_class, Class):
                             # it's a class method
                             f = Function(None, meth_name, None, lineno,
-                                         meth_sig, meth_pyqtSig)
+                                         meth_sig, meth_pyqtSig, modifierType=modifier)
                             self.__py_setVisibility(f)
                             cur_class.addMethod(meth_name, f)
                             break
                     else:
                         # it's a nested function of a module function
                         f = Function(self.name, meth_name, self.file, lineno,
-                                     meth_sig, meth_pyqtSig)
+                                     meth_sig, meth_pyqtSig, modifierType=modifier)
                         self.__py_setVisibility(f)
                         self.addFunction(meth_name, f)
                 else:
                     # it's a module function
                     f = Function(self.name, meth_name, self.file, lineno,
-                                 meth_sig, meth_pyqtSig)
+                                 meth_sig, meth_pyqtSig, modifierType=modifier)
                     self.__py_setVisibility(f)
                     self.addFunction(meth_name, f)
                 cur_obj = f
                 classstack.append((None, thisindent))  # Marker for nested fns
+                
+                # reset the modifier settings
+                modifierType = Function.General
+                modifierIndent = -1
             
             elif m.start("Docstring") >= 0:
                 contents = m.group("DocstringContents3")
@@ -1216,7 +1240,12 @@
     '''
     Class to represent a Python function or method.
     '''
-    def __init__(self, module, name, file, lineno, signature='', pyqtSignature=None):
+    General = 0
+    Static = 1
+    Class = 2
+    
+    def __init__(self, module, name, file, lineno, signature='', pyqtSignature=None,
+                 modifierType=General):
         """
         Constructor
         
@@ -1226,6 +1255,7 @@
         @param lineno linenumber of the function definition (integer)
         @param signature the functions call signature (string)
         @param pyqtSignature the functions PyQt signature (string)
+        @param modifierType type of the function
         """
         self.module = module
         self.name = name
@@ -1235,6 +1265,7 @@
         self.parameters = [e.strip() for e in signature.split(',')]
         self.description = ""
         self.pyqtSignature = pyqtSignature
+        self.modifier = modifierType
         self.setPublic()
     
     def addDescription(self, description):
--- a/changelog	Sat Aug 20 10:49:36 2011 +0200
+++ b/changelog	Sat Aug 20 16:28:25 2011 +0200
@@ -8,6 +8,10 @@
      Editor->Filehandling page
 - enhancements of the cooperation functions
   -- added code to the cooperation functions to support IPv6
+- enhancements to the source browser
+  -- show class attributes, class methods and static methods with different icons
+- enhancements to the source documentor
+  -- introduced separate sections for class methods and static methods
 
 Version 5.2-snapshot-20110724:
 - bug fixes
Binary file icons/default/attribute_class.png has changed
Binary file icons/default/attributes_class.png has changed
Binary file icons/default/method_class.png has changed
Binary file icons/default/method_static.png has changed

eric ide

mercurial