Utilities/ClassBrowsers/rbclbr.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utilities/ClassBrowsers/rbclbr.py	Mon Dec 28 16:03:33 2009 +0000
@@ -0,0 +1,536 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2005 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Parse a Ruby file and retrieve classes, modules, methods and attributes.
+
+Parse enough of a Ruby file to recognize class, module and method definitions
+and to find out the superclasses of a class as well as its attributes.
+
+It is based on the Python class browser found in this package.
+"""
+
+import sys
+import os
+import re
+
+import Utilities
+import Utilities.ClassBrowsers as ClassBrowsers
+import ClbrBaseClasses
+
+SUPPORTED_TYPES = [ClassBrowsers.RB_SOURCE]
+    
+_getnext = re.compile(r"""
+    (?P<String>
+        =begin .*? =end
+    
+    |   <<-? (?P<HereMarker1> [a-zA-Z0-9_]+? ) [ \t]* .*? (?P=HereMarker1)
+
+    |   <<-? ['"] (?P<HereMarker2> [^'"]+? ) ['"] [ \t]* .*? (?P=HereMarker2)
+
+    |   " [^"\\\n]* (?: \\. [^"\\\n]*)* "
+
+    |   ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
+    )
+
+|   (?P<CodingLine>
+        ^ \# \s* [*_-]* \s* coding[:=] \s* (?P<Coding> [-\w_.]+ ) \s* [*_-]* $
+    )
+
+|   (?P<Comment>
+        ^
+        [ \t]* \#+ .*? $
+    )
+
+|   (?P<Method>
+        ^
+        (?P<MethodIndent> [ \t]* )
+        def [ \t]+
+        (?:
+            (?P<MethodName2> [a-zA-Z0-9_]+ (?: \. | :: ) [a-zA-Z_] [a-zA-Z0-9_?!=]* )
+        |
+            (?P<MethodName> [a-zA-Z_] [a-zA-Z0-9_?!=]* )
+        |
+            (?P<MethodName3> [^( \t]{1,3} )
+        )
+        [ \t]* 
+        (?: 
+            \( (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? ) \)
+        )?
+        [ \t]*
+    )
+
+|   (?P<Class>
+        ^
+        (?P<ClassIndent> [ \t]* )
+        class
+        (?:
+            [ \t]+
+            (?P<ClassName> [A-Z] [a-zA-Z0-9_]* )
+            [ \t]*
+            (?P<ClassSupers> < [ \t]* [A-Z] [a-zA-Z0-9_:]* )?
+        |
+            [ \t]* << [ \t]* 
+            (?P<ClassName2> [a-zA-Z_] [a-zA-Z0-9_:]* )
+        )
+        [ \t]*
+    )
+
+|   (?P<ClassIgnored>
+        \(
+        [ \t]*
+        class
+        .*?
+        end
+        [ \t]*
+        \)
+    )
+
+|   (?P<Module>
+        ^
+        (?P<ModuleIndent> [ \t]* )
+        module [ \t]+
+        (?P<ModuleName> [A-Z] [a-zA-Z0-9_:]* )
+        [ \t]*
+    )
+
+|   (?P<AccessControl>
+        ^
+        (?P<AccessControlIndent> [ \t]* )
+        (?:
+            (?P<AccessControlType> private | public | protected ) [^_]
+        |
+            (?P<AccessControlType2> private_class_method | public_class_method )
+        )
+        \(?
+        [ \t]*
+        (?P<AccessControlList> (?: : [a-zA-Z0-9_]+ , \s* )* (?: : [a-zA-Z0-9_]+ )+ )?
+        [ \t]*
+        \)?
+    )
+
+|   (?P<Attribute>
+        ^
+        (?P<AttributeIndent> [ \t]* )
+        (?P<AttributeName> (?: @ | @@ ) [a-zA-Z0-9_]* )
+        [ \t]* =
+    )
+
+|   (?P<Attr>
+        ^
+        (?P<AttrIndent> [ \t]* )
+        attr 
+        (?P<AttrType> (?: _accessor | _reader | _writer ) )?
+        \(?
+        [ \t]*
+        (?P<AttrList> (?: : [a-zA-Z0-9_]+ , \s* )* (?: : [a-zA-Z0-9_]+ | true | false )+ )
+        [ \t]*
+        \)?
+    )
+
+|   (?P<Begin>
+            ^
+            [ \t]*
+            (?: def | if | unless | case | while | until | for | begin ) \b [^_]
+        |
+            [ \t]* do [ \t]* (?: \| .*? \| )? [ \t]* $
+    )
+
+|   (?P<BeginEnd>
+        \b (?: if ) \b [^_] .*? $
+        |
+        \b (?: if ) \b [^_] .*? end [ \t]* $
+    )
+
+|   (?P<End>
+        [ \t]*
+        (?:
+            end [ \t]* $
+        |
+            end \b [^_]
+        )
+    )
+""", re.VERBOSE | re.DOTALL | re.MULTILINE).search
+
+_commentsub = re.compile(r"""#[^\n]*\n|#[^\n]*$""").sub
+
+_modules = {}                           # cache of modules we've seen
+
+class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase):
+    """
+    Mixin class implementing the notion of visibility.
+    """
+    def __init__(self):
+        """
+        Method to initialize the visibility.
+        """
+        self.setPublic()
+
+class Class(ClbrBaseClasses.Class, VisibilityMixin):
+    """
+    Class to represent a Ruby class.
+    """
+    def __init__(self, module, name, super, file, lineno):
+        """
+        Constructor
+        
+        @param module name of the module containing this class
+        @param name name of this class
+        @param super list of class names this class is inherited from
+        @param file filename containing this class
+        @param lineno linenumber of the class definition
+        """
+        ClbrBaseClasses.Class.__init__(self, module, name, super, file, lineno)
+        VisibilityMixin.__init__(self)
+
+class Module(ClbrBaseClasses.Module, VisibilityMixin):
+    """
+    Class to represent a Ruby module.
+    """
+    def __init__(self, module, name, file, lineno):
+        """
+        Constructor
+        
+        @param module name of the module containing this class
+        @param name name of this class
+        @param file filename containing this class
+        @param lineno linenumber of the class definition
+        """
+        ClbrBaseClasses.Module.__init__(self, module, name, file, lineno)
+        VisibilityMixin.__init__(self)
+
+class Function(ClbrBaseClasses.Function, VisibilityMixin):
+    """
+    Class to represent a Ruby function.
+    """
+    def __init__(self, module, name, file, lineno, signature = '', separator = ','):
+        """
+        Constructor
+        
+        @param module name of the module containing this function
+        @param name name of this function
+        @param file filename containing this class
+        @param lineno linenumber of the class definition
+        @param signature parameterlist of the method
+        @param separator string separating the parameters
+        """
+        ClbrBaseClasses.Function.__init__(self, module, name, file, lineno, 
+                                          signature, separator)
+        VisibilityMixin.__init__(self)
+
+class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin):
+    """
+    Class to represent a class or module attribute.
+    """
+    def __init__(self, module, name, file, lineno):
+        """
+        Constructor
+        
+        @param module name of the module containing this class
+        @param name name of this class
+        @param file filename containing this attribute
+        @param lineno linenumber of the class definition
+        """
+        ClbrBaseClasses.Attribute.__init__(self, module, name, file, lineno)
+        VisibilityMixin.__init__(self)
+        self.setPrivate()
+
+def readmodule_ex(module, path=[]):
+    '''
+    Read a Ruby file and return a dictionary of classes, functions and modules.
+
+    @param module name of the Ruby file (string)
+    @param path path the file should be searched in (list of strings)
+    @return the resulting dictionary
+    '''
+
+    dict = {}
+    dict_counts = {}
+
+    if module in _modules:
+        # we've seen this file before...
+        return _modules[module]
+
+    # search the path for the file
+    f = None
+    fullpath = list(path)
+    f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
+    if type not in SUPPORTED_TYPES:
+        # not Ruby source, can't do anything with this module
+        f.close()
+        _modules[module] = dict
+        return dict
+
+    _modules[module] = dict
+    classstack = [] # stack of (class, indent) pairs
+    acstack = []    # stack of (access control, indent) pairs
+    indent = 0
+    src = Utilities.decode(f.read())[0]
+    f.close()
+
+    lineno, last_lineno_pos = 1, 0
+    i = 0
+    while 1:
+        m = _getnext(src, i)
+        if not m:
+            break
+        start, i = m.span()
+
+        if m.start("Method") >= 0:
+            # found a method definition or function
+            thisindent = indent
+            indent += 1
+            meth_name = m.group("MethodName") or \
+                        m.group("MethodName2") or \
+                        m.group("MethodName3")
+            meth_sig = m.group("MethodSignature")
+            meth_sig = meth_sig and meth_sig.replace('\\\n', '') or ''
+            meth_sig = _commentsub('', meth_sig)
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            if meth_name.startswith('self.'):
+                meth_name = meth_name[5:]
+            elif meth_name.startswith('self::'):
+                meth_name = meth_name[6:]
+            # close all classes/modules indented at least as much
+            while classstack and \
+                  classstack[-1][1] >= thisindent:
+                del classstack[-1]
+            while acstack and \
+                  acstack[-1][1] >= thisindent:
+                del acstack[-1]
+            if classstack:
+                # it's a class/module method
+                cur_class = classstack[-1][0]
+                if isinstance(cur_class, Class) or isinstance(cur_class, Module):
+                    # it's a method
+                    f = Function(None, meth_name,
+                                 file, lineno, meth_sig)
+                    cur_class._addmethod(meth_name, f)
+                else:
+                    f = cur_class
+                # set access control
+                if acstack:
+                    accesscontrol = acstack[-1][0]
+                    if accesscontrol == "private":
+                        f.setPrivate()
+                    elif accesscontrol == "protected":
+                        f.setProtected()
+                    elif accesscontrol == "public":
+                        f.setPublic()
+                # else it's a nested def
+            else:
+                # it's a function
+                f = Function(module, meth_name,
+                             file, lineno, meth_sig)
+                if dict_counts.has_key(meth_name):
+                    dict_counts[meth_name] += 1
+                    meth_name = "%s_%d" % (meth_name, dict_counts[meth_name])
+                else:
+                    dict_counts[meth_name] = 0
+                dict[meth_name] = f
+            classstack.append((f, thisindent)) # Marker for nested fns
+
+        elif m.start("String") >= 0:
+            pass
+
+        elif m.start("Comment") >= 0:
+            pass
+
+        elif m.start("ClassIgnored") >= 0:
+            pass
+
+        elif m.start("Class") >= 0:
+            # we found a class definition
+            thisindent = indent
+            indent += 1
+            # close all classes/modules indented at least as much
+            while classstack and \
+                  classstack[-1][1] >= thisindent:
+                del classstack[-1]
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            class_name = m.group("ClassName") or m.group("ClassName2")
+            inherit = m.group("ClassSupers")
+            if inherit:
+                # the class inherits from other classes
+                inherit = inherit[1:].strip()
+                inherit = [_commentsub('', inherit)]
+            # remember this class
+            cur_class = Class(module, class_name, inherit,
+                              file, lineno)
+            if not classstack:
+                if dict.has_key(class_name):
+                    cur_class = dict[class_name]
+                else:
+                    dict[class_name] = cur_class
+            else:
+                cls = classstack[-1][0]
+                if cls.classes.has_key(class_name):
+                    cur_class = cls.classes[class_name]
+                elif cls.name == class_name or class_name == "self":
+                    cur_class = cls
+                else:
+                    cls._addclass(class_name, cur_class)
+            classstack.append((cur_class, thisindent))
+            while acstack and \
+                  acstack[-1][1] >= thisindent:
+                del acstack[-1]
+            acstack.append(["public", thisindent])  # default access control is 'public'
+
+        elif m.start("Module") >= 0:
+            # we found a module definition
+            thisindent = indent
+            indent += 1
+            # close all classes/modules indented at least as much
+            while classstack and \
+                  classstack[-1][1] >= thisindent:
+                del classstack[-1]
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            module_name = m.group("ModuleName")
+            # remember this class
+            cur_class = Module(module, module_name, file, lineno)
+            if not classstack:
+                if dict.has_key(module_name):
+                    cur_class = dict[module_name]
+                else:
+                    dict[module_name] = cur_class
+            else:
+                cls = classstack[-1][0]
+                if cls.classes.has_key(module_name):
+                    cur_class = cls.classes[module_name]
+                elif cls.name == module_name:
+                    cur_class = cls
+                else:
+                    cls._addclass(module_name, cur_class)
+            classstack.append((cur_class, thisindent))
+            while acstack and \
+                  acstack[-1][1] >= thisindent:
+                del acstack[-1]
+            acstack.append(["public", thisindent])  # default access control is 'public'
+
+        elif m.start("AccessControl") >= 0:
+            aclist = m.group("AccessControlList")
+            if aclist is None:
+                index = -1
+                while index >= -len(acstack):
+                    if acstack[index][1] < indent:
+                        actype = m.group("AccessControlType") or \
+                                 m.group("AccessControlType2").split('_')[0]
+                        acstack[index][0] = actype.lower()
+                        break
+                    else:
+                        index -= 1
+            else:
+                index = -1
+                while index >= -len(classstack):
+                    if classstack[index][0] is not None and \
+                       not isinstance(classstack[index][0], Function) and \
+                       not classstack[index][1] >= indent:
+                        parent = classstack[index][0]
+                        actype = m.group("AccessControlType") or \
+                                 m.group("AccessControlType2").split('_')[0]
+                        actype = actype.lower()
+                        for name in aclist.split(","):
+                            name = name.strip()[1:]     # get rid of leading ':'
+                            acmeth = parent._getmethod(name)
+                            if acmeth is None:
+                                continue
+                            if actype == "private":
+                                acmeth.setPrivate()
+                            elif actype == "protected":
+                                acmeth.setProtected()
+                            elif actype == "public":
+                                acmeth.setPublic()
+                        break
+                    else:
+                        index -= 1
+
+        elif m.start("Attribute") >= 0:
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            index = -1
+            while index >= -len(classstack):
+                if classstack[index][0] is not None and \
+                   not isinstance(classstack[index][0], Function) and \
+                   not classstack[index][1] >= indent:
+                    attr = Attribute(module, m.group("AttributeName"), file, lineno)
+                    classstack[index][0]._addattribute(attr)
+                    break
+                else:
+                    index -= 1
+
+        elif m.start("Attr") >= 0:
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            index = -1
+            while index >= -len(classstack):
+                if classstack[index][0] is not None and \
+                   not isinstance(classstack[index][0], Function) and \
+                   not classstack[index][1] >= indent:
+                    parent = classstack[index][0]
+                    if m.group("AttrType") is None:
+                        nv = m.group("AttrList").split(",")
+                        if not nv:
+                            break
+                        name = nv[0].strip()[1:]    # get rid of leading ':'
+                        attr = parent._getattribute("@"+name) or \
+                               parent._getattribute("@@"+name) or \
+                               Attribute(module, "@"+name, file, lineno)
+                        if len(nv) == 1 or nv[1].strip() == "false":
+                            attr.setProtected()
+                        elif nv[1].strip() == "true":
+                            attr.setPublic()
+                        parent._addattribute(attr)
+                    else:
+                        access = m.group("AttrType")
+                        for name in m.group("AttrList").split(","):
+                            name = name.strip()[1:]     # get rid of leading ':'
+                            attr = parent._getattribute("@"+name) or \
+                                   parent._getattribute("@@"+name) or \
+                                   Attribute(module, "@"+name, file, lineno)
+                            if access == "_accessor":
+                                attr.setPublic()
+                            elif access == "_reader" or access == "_writer":
+                                if attr.isPrivate():
+                                    attr.setProtected()
+                                elif attr.isProtected():
+                                    attr.setPublic()
+                            parent._addattribute(attr)
+                    break
+                else:
+                    index -= 1
+
+        elif m.start("Begin") >= 0:
+            # a begin of a block we are not interested in
+            indent += 1
+
+        elif m.start("End") >= 0:
+            # an end of a block
+            indent -= 1
+            if indent < 0:
+                # no negative indent allowed
+                if classstack:
+                    # it's a class/module method
+                    indent = classstack[-1][1]
+                else:
+                    indent = 0
+        
+        elif m.start("BeginEnd") >= 0:
+            pass
+        
+        elif m.start("CodingLine") >= 0:
+            # a coding statement
+            coding = m.group("Coding")
+            lineno = lineno + src.count('\n', last_lineno_pos, start)
+            last_lineno_pos = start
+            if not dict.has_key("@@Coding@@"):
+                dict["@@Coding@@"] = ClbrBaseClasses.Coding(module, file, lineno, coding)
+
+        else:
+            assert 0, "regexp _getnext found something unexpected"
+
+    return dict

eric ide

mercurial