Fix for numeric var types creating an error.
[blender.git] / release / scripts / bpymodules / BPyTextPlugin.py
index 8b67937bb9e07cbb0bfcd918b2c54a03d39de498..5e38725b75fda6083bd7e23c8de8cbbe7aa6c80f 100644 (file)
@@ -1,18 +1,42 @@
-import bpy, sys
+"""The BPyTextPlugin Module
+
+Use get_cached_descriptor(txt) to retrieve information about the script held in
+the txt Text object.
+
+Use print_cache_for(txt) to print the information to the console.
+
+Use line, cursor = current_line(txt) to get the logical line and cursor position
+
+Use get_targets(line, cursor) to find out what precedes the cursor:
+       aaa.bbb.cc|c.ddd -> ['aaa', 'bbb', 'cc']
+
+Use resolve_targets(txt, targets) to turn a target list into a usable object if
+one is found to match.
+"""
+
+import bpy, sys, os
 import __builtin__, tokenize
 from Blender.sys import time
-from tokenize import generate_tokens, TokenError
+from tokenize import generate_tokens, TokenError, \
+               COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING, NUMBER
 
-# TODO: Remove the dependency for a full Python installation.
-
-class ClassDesc():
+class Definition():
+       """Describes a definition or defined object through its name, line number
+       and docstring. This is the base class for definition based descriptors.
+       """
        
-       def __init__(self, name, defs, vars):
+       def __init__(self, name, lineno, doc=''):
                self.name = name
-               self.defs = defs
-               self.vars = vars
+               self.lineno = lineno
+               self.doc = doc
 
 class ScriptDesc():
+       """Describes a script through lists of further descriptor objects (classes,
+       defs, vars) and dictionaries to built-in types (imports). If a script has
+       not been fully parsed, its incomplete flag will be set. The time of the last
+       parse is held by the time field and the name of the text object from which
+       it was parsed, the name field.
+       """
        
        def __init__(self, name, imports, classes, defs, vars, incomplete=False):
                self.name = name
@@ -21,20 +45,47 @@ class ScriptDesc():
                self.defs = defs
                self.vars = vars
                self.incomplete = incomplete
-               self.time = 0
+               self.parse_due = 0
        
-       def set_time(self):
-               self.time = time()
+       def set_delay(self, delay):
+               self.parse_due = time() + delay
 
-# Context types
-UNSET = -1
-NORMAL = 0
-SINGLE_QUOTE = 1
-DOUBLE_QUOTE = 2
-COMMENT = 3
+class ClassDesc(Definition):
+       """Describes a class through lists of further descriptor objects (defs and
+       vars). The name of the class is held by the name field and the line on
+       which it is defined is held in lineno.
+       """
+       
+       def __init__(self, name, defs, vars, lineno, doc=''):
+               Definition.__init__(self, name, lineno, doc)
+               self.defs = defs
+               self.vars = vars
 
-# Special period constants
-AUTO = -1
+class FunctionDesc(Definition):
+       """Describes a function through its name and list of parameters (name,
+       params) and the line on which it is defined (lineno).
+       """
+       
+       def __init__(self, name, params, lineno, doc=''):
+               Definition.__init__(self, name, lineno, doc)
+               self.params = params
+
+class VarDesc(Definition):
+       """Describes a variable through its name and type (if ascertainable) and the
+       line on which it is defined (lineno). If no type can be determined, type
+       will equal None.
+       """
+       
+       def __init__(self, name, type, lineno):
+               Definition.__init__(self, name, lineno)
+               self.type = type # None for unknown (supports: dict/list/str)
+
+# Context types
+CTX_UNSET = -1
+CTX_NORMAL = 0
+CTX_SINGLE_QUOTE = 1
+CTX_DOUBLE_QUOTE = 2
+CTX_COMMENT = 3
 
 # Python keywords
 KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
@@ -43,47 +94,125 @@ KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
                        'raise', 'continue', 'finally', 'is', 'return', 'def', 'for',
                        'lambda', 'try' ]
 
+# Module file extensions
+MODULE_EXTS = ['.py', '.pyc', '.pyo', '.pyw', '.pyd']
+
 ModuleType = type(__builtin__)
 NoneScriptDesc = ScriptDesc('', dict(), dict(), dict(), dict(), True)
 
-_modules = dict([(n, None) for n in sys.builtin_module_names])
+_modules = {}
 _modules_updated = 0
 _parse_cache = dict()
 
-def get_cached_descriptor(txt, period=AUTO):
+def _load_module_names():
+       """Searches the sys.path for module files and lists them, along with
+       sys.builtin_module_names, in the global dict _modules.
+       """
+       
+       global _modules
+       
+       for n in sys.builtin_module_names:
+               _modules[n] = None
+       for p in sys.path:
+               if p == '': p = os.curdir
+               if not os.path.isdir(p): continue
+               for f in os.listdir(p):
+                       for ext in MODULE_EXTS:
+                               if f.endswith(ext):
+                                       _modules[f[:-len(ext)]] = None
+                                       break
+
+_load_module_names()
+
+def _trim_doc(doc):
+       """Trims the quotes from a quoted STRING token (eg. "'''text'''" -> "text")
+       """
+       
+       l = len(doc)
+       i = 0
+       while i < l/2 and (doc[i] == "'" or doc[i] == '"'):
+               i += 1
+       return doc[i:-i]
+
+def resolve_targets(txt, targets):
+       """Attempts to return a useful object for the locally or externally defined
+       entity described by targets. If the object is local (defined in txt), a
+       Definition instance is returned. If the object is external (imported or
+       built in), the object itself is returned. If no object can be found, None is
+       returned.
+       """
+       
+       count = len(targets)
+       if count==0: return None
+       
+       obj = None
+       local = None
+       i = 1
+       
+       desc = get_cached_descriptor(txt)
+       if desc.classes.has_key(targets[0]):
+               local = desc.classes[targets[0]]
+       elif desc.defs.has_key(targets[0]):
+               local = desc.defs[targets[0]]
+       elif desc.vars.has_key(targets[0]):
+               obj = desc.vars[targets[0]].type
+       
+       if local:
+               while i < count:
+                       if hasattr(local, 'classes') and local.classes.has_key(targets[i]):
+                               local = local.classes[targets[i]]
+                       elif hasattr(local, 'defs') and local.defs.has_key(targets[i]):
+                               local = local.defs[targets[i]]
+                       elif hasattr(local, 'vars') and local.vars.has_key(targets[i]):
+                               obj = local.vars[targets[i]].type
+                               local = None
+                               i += 1
+                               break
+                       else:
+                               local = None
+                               break
+                       i += 1
+       
+       if local: return local
+       
+       if not obj:
+               if desc.imports.has_key(targets[0]):
+                       obj = desc.imports[targets[0]]
+               else:
+                       builtins = get_builtins()
+                       if builtins.has_key(targets[0]):
+                               obj = builtins[targets[0]]
+       
+       while obj and i < count:
+               if hasattr(obj, targets[i]):
+                       obj = getattr(obj, targets[i])
+               else:
+                       obj = None
+                       break
+               i += 1
+       
+       return obj
+
+def get_cached_descriptor(txt, force_parse=0):
        """Returns the cached ScriptDesc for the specified Text object 'txt'. If the
        script has not been parsed in the last 'period' seconds it will be reparsed
        to obtain this descriptor.
        
-       Specifying AUTO for the period (default) will choose a period based on the
+       Specifying TP_AUTO for the period (default) will choose a period based on the
        size of the Text object. Larger texts are parsed less often.
        """
        
-       global _parse_cache, NoneScriptDesc, AUTO
-       
-       if period == AUTO:
-               m = txt.nlines
-               r = 1
-               while True:
-                       m = m >> 2
-                       if not m: break
-                       r = r << 1
-               period = r
+       global _parse_cache
        
-       key = hash(txt)
        parse = True
-       if _parse_cache.has_key(key):
+       key = hash(txt)
+       if not force_parse and _parse_cache.has_key(key):
                desc = _parse_cache[key]
-               if desc.time >= time() - period:
+               if desc.parse_due > time():
                        parse = desc.incomplete
        
        if parse:
-               try:
-                       desc = parse_text(txt)
-               except:
-                       if _parse_cache.has_key(key):
-                               del _parse_cache[key]
-                       desc = NoneScriptDesc
+               desc = parse_text(txt)
        
        return desc
 
@@ -91,13 +220,13 @@ def parse_text(txt):
        """Parses an entire script's text and returns a ScriptDesc instance
        containing information about the script.
        
-       If the text is not a valid Python script a TokenError will be thrown.
-       Currently this means leaving brackets open will result in the script failing
-       to complete.
+       If the text is not a valid Python script (for example if brackets are left
+       open), parsing may fail to complete. However, if this occurs, no exception
+       is thrown. Instead the returned ScriptDesc instance will have its incomplete
+       flag set and information processed up to this point will still be accessible.
        """
        
-       global NORMAL, SINGLE_QUOTE, DOUBLE_QUOTE, COMMENT
-       
+       start_time = time()
        txt.reset()
        tokens = generate_tokens(txt.readline) # Throws TokenError
        
@@ -114,25 +243,37 @@ def parse_text(txt):
        def_step = 0
        
        vars = dict()
-       var_step = 0
+       var1_step = 0
+       var2_step = 0
+       var3_step = 0
        var_accum = dict()
        var_forflag = False
        
        indent = 0
        prev_type = -1
-       prev_string = ''
+       prev_text = ''
        incomplete = False
        
-       try:
-        for type, string, start, end, line in tokens:
+       while True:
+               try:
+                       type, text, start, end, line = tokens.next()
+               except StopIteration:
+                       break
+               except TokenError, IndentationError:
+                       incomplete = True
+                       break
+               
+               # Skip all comments and line joining characters
+               if type == COMMENT or type == NL:
+                       continue
                
                #################
                ## Indentation ##
                #################
                
-               if type == tokenize.INDENT:
+               if type == INDENT:
                        indent += 1
-               elif type == tokenize.DEDENT:
+               elif type == DEDENT:
                        indent -= 1
                
                #########################
@@ -143,41 +284,41 @@ def parse_text(txt):
                
                # Default, look for 'from' or 'import' to start
                if imp_step == 0:
-                       if string == 'from':
+                       if text == 'from':
                                imp_tmp = []
                                imp_step = 1
-                       elif string == 'import':
+                       elif text == 'import':
                                imp_from = None
                                imp_tmp = []
                                imp_step = 2
                
                # Found a 'from', create imp_from in form '???.???...'
                elif imp_step == 1:
-                       if string == 'import':
+                       if text == 'import':
                                imp_from = '.'.join(imp_tmp)
                                imp_tmp = []
                                imp_step = 2
-                       elif type == tokenize.NAME:
-                               imp_tmp.append(string)
-                       elif string != '.':
+                       elif type == NAME:
+                               imp_tmp.append(text)
+                       elif text != '.':
                                imp_step = 0 # Invalid syntax
                
                # Found 'import', imp_from is populated or None, create imp_name
                elif imp_step == 2:
-                       if string == 'as':
+                       if text == 'as':
                                imp_name = '.'.join(imp_tmp)
                                imp_step = 3
-                       elif type == tokenize.NAME or string == '*':
-                               imp_tmp.append(string)
-                       elif string != '.':
+                       elif type == NAME or text == '*':
+                               imp_tmp.append(text)
+                       elif text != '.':
                                imp_name = '.'.join(imp_tmp)
                                imp_symb = imp_name
                                imp_store = True
                
                # Found 'as', change imp_symb to this value and go back to step 2
                elif imp_step == 3:
-                       if type == tokenize.NAME:
-                               imp_symb = string
+                       if type == NAME:
+                               imp_symb = text
                        else:
                                imp_store = True
                
@@ -196,7 +337,6 @@ def parse_text(txt):
                                                module = get_module(imp_from +'.'+ imp_name)
                                        else:
                                                module = get_module(imp_name)
-                                       imports[imp_symb] = module
                                except (ImportError, ValueError, AttributeError, TypeError):
                                        # Try importing name as an attribute of the parent
                                        try:
@@ -204,9 +344,11 @@ def parse_text(txt):
                                                imports[imp_symb] = getattr(module, imp_name)
                                        except (ImportError, ValueError, AttributeError, TypeError):
                                                pass
+                               else:
+                                       imports[imp_symb] = module
                        
                        # More to import from the same module?
-                       if string == ',':
+                       if text == ',':
                                imp_tmp = []
                                imp_step = 2
                        else:
@@ -221,39 +363,42 @@ def parse_text(txt):
                
                # Look for 'class'
                if cls_step == 0:
-                       if string == 'class':
+                       if text == 'class':
                                cls_name = None
+                               cls_lineno = start[0]
                                cls_indent = indent
                                cls_step = 1
                
                # Found 'class', look for cls_name followed by '('
                elif cls_step == 1:
                        if not cls_name:
-                               if type == tokenize.NAME:
-                                       cls_name = string
+                               if type == NAME:
+                                       cls_name = text
                                        cls_sline = False
                                        cls_defs = dict()
                                        cls_vars = dict()
-                       elif string == ':':
+                       elif text == ':':
                                cls_step = 2
                
                # Found 'class' name ... ':', now check if it's a single line statement
                elif cls_step == 2:
-                       if type == tokenize.NEWLINE:
+                       if type == NEWLINE:
                                cls_sline = False
-                               cls_step = 3
-                       elif type != tokenize.COMMENT and type != tokenize.NL:
+                       else:
                                cls_sline = True
-                               cls_step = 3
+                       cls_doc = ''
+                       cls_step = 3
                
                elif cls_step == 3:
+                       if not cls_doc and type == STRING:
+                               cls_doc = _trim_doc(text)
                        if cls_sline:
-                               if type == tokenize.NEWLINE:
-                                       classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars)
+                               if type == NEWLINE:
+                                       classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
                                        cls_step = 0
                        else:
-                               if type == tokenize.DEDENT and indent <= cls_indent:
-                                       classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars)
+                               if type == DEDENT and indent <= cls_indent:
+                                       classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
                                        cls_step = 0
                
                #################
@@ -262,28 +407,51 @@ def parse_text(txt):
                
                # Look for 'def'
                if def_step == 0:
-                       if string == 'def':
+                       if text == 'def':
                                def_name = None
+                               def_lineno = start[0]
                                def_step = 1
                
                # Found 'def', look for def_name followed by '('
                elif def_step == 1:
-                       if type == tokenize.NAME:
-                               def_name = string
+                       if type == NAME:
+                               def_name = text
                                def_params = []
-                       elif def_name and string == '(':
+                       elif def_name and text == '(':
                                def_step = 2
                
                # Found 'def' name '(', now identify the parameters upto ')'
                # TODO: Handle ellipsis '...'
                elif def_step == 2:
-                       if type == tokenize.NAME:
-                               def_params.append(string)
-                       elif string == ')':
+                       if type == NAME:
+                               def_params.append(text)
+                       elif text == ':':
+                               def_step = 3
+               
+               # Found 'def' ... ':', now check if it's a single line statement
+               elif def_step == 3:
+                       if type == NEWLINE:
+                               def_sline = False
+                       else:
+                               def_sline = True
+                       def_doc = ''
+                       def_step = 4
+               
+               elif def_step == 4:
+                       if type == STRING:
+                               def_doc = _trim_doc(text)
+                       newdef = None
+                       if def_sline:
+                               if type == NEWLINE:
+                                       newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
+                       else:
+                               if type == NAME:
+                                       newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
+                       if newdef:
                                if cls_step > 0: # Parsing a class
-                                       cls_defs[def_name] = def_params
+                                       cls_defs[def_name] = newdef
                                else:
-                                       defs[def_name] = def_params
+                                       defs[def_name] = newdef
                                def_step = 0
                
                ##########################
@@ -292,68 +460,108 @@ def parse_text(txt):
                
                if cls_step > 0: # Parsing a class
                        # Look for 'self.???'
-                       if var_step == 0:
-                               if string == 'self':
-                                       var_step = 1
-                       elif var_step == 1:
-                               if string == '.':
+                       if var1_step == 0:
+                               if text == 'self':
+                                       var1_step = 1
+                       elif var1_step == 1:
+                               if text == '.':
                                        var_name = None
-                                       var_step = 2
+                                       var1_step = 2
                                else:
-                                       var_step = 0
-                       elif var_step == 2:
-                               if type == tokenize.NAME:
-                                       var_name = string
-                                       var_step = 3
-                       elif var_step == 3:
-                               if string == '=':
-                                       cls_vars[var_name] = True
-                                       var_step = 0
+                                       var1_step = 0
+                       elif var1_step == 2:
+                               if type == NAME:
+                                       var_name = text
+                                       if cls_vars.has_key(var_name):
+                                               var_step = 0
+                                       else:
+                                               var1_step = 3
+                       elif var1_step == 3:
+                               if text == '=':
+                                       var1_step = 4
+                       elif var1_step == 4:
+                               var_type = None
+                               if type == NUMBER:
+                                       close = end[1]
+                                       if text.find('.') != -1: var_type = float
+                                       else: var_type = int
+                               elif type == STRING:
+                                       close = end[1]
+                                       var_type = str
+                               elif text == '[':
+                                       close = line.find(']', end[1])
+                                       var_type = list
+                               elif text == '(':
+                                       close = line.find(')', end[1])
+                                       var_type = tuple
+                               elif text == '{':
+                                       close = line.find('}', end[1])
+                                       var_type = dict
+                               elif text == 'dict':
+                                       close = line.find(')', end[1])
+                                       var_type = dict
+                               if var_type and close+1 < len(line):
+                                       if line[close+1] != ' ' and line[close+1] != '\t':
+                                               var_type = None
+                               cls_vars[var_name] = VarDesc(var_name, var_type, start[0])
+                               var1_step = 0
                
                elif def_step > 0: # Parsing a def
                        # Look for 'global ???[,???]'
-                       if var_step == 0:
-                               if string == 'global':
-                                       var_step = 1
-                       elif var_step == 1:
-                               if type == tokenize.NAME:
-                                       vars[string] = True
-                               elif string != ',' and type != tokenize.NL:
-                                       var_step == 0
+                       if var2_step == 0:
+                               if text == 'global':
+                                       var2_step = 1
+                       elif var2_step == 1:
+                               if type == NAME:
+                                       if not vars.has_key(text):
+                                               vars[text] = VarDesc(text, None, start[0])
+                               elif text != ',' and type != NL:
+                                       var2_step == 0
                
                else: # In global scope
-                       # Look for names
-                       if string == 'for':
-                               var_accum = dict()
-                               var_forflag = True
-                       elif string == '=' or (var_forflag and string == 'in'):
-                               vars.update(var_accum)
-                               var_accum = dict()
-                               var_forflag = False
-                       elif type == tokenize.NAME:
-                               var_accum[string] = True
-                       elif not string in [',', '(', ')', '[', ']']:
-                               var_accum = dict()
-                               var_forflag = False
+                       if var3_step == 0:
+                               # Look for names
+                               if text == 'for':
+                                       var_accum = dict()
+                                       var_forflag = True
+                               elif text == '=' or (var_forflag and text == 'in'):
+                                       var_forflag = False
+                                       var3_step = 1
+                               elif type == NAME:
+                                       if prev_text != '.' and not vars.has_key(text):
+                                               var_accum[text] = VarDesc(text, None, start[0])
+                               elif not text in [',', '(', ')', '[', ']']:
+                                       var_accum = dict()
+                                       var_forflag = False
+                       elif var3_step == 1:
+                               if len(var_accum) != 1:
+                                       var_type = None
+                                       vars.update(var_accum)
+                               else:
+                                       var_name = var_accum.keys()[0]
+                                       var_type = None
+                                       if type == NUMBER:
+                                               if text.find('.') != -1: var_type = float
+                                               else: var_type = int
+                                       elif type == STRING: var_type = str
+                                       elif text == '[': var_type = list
+                                       elif text == '(': var_type = tuple
+                                       elif text == '{': var_type = dict
+                                       vars[var_name] = VarDesc(var_name, var_type, start[0])
+                               var3_step = 0
                
                #######################
                ## General utilities ##
                #######################
                
                prev_type = type
-               prev_string = string
-       
-        # end:for
-       
-       except TokenError:
-               incomplete = True
-               pass
+               prev_text = text
        
        desc = ScriptDesc(txt.name, imports, classes, defs, vars, incomplete)
-       desc.set_time()
+       desc.set_delay(10 * (time()-start_time) + 0.05)
        
        global _parse_cache
-       _parse_cache[hash(txt.name)] = desc
+       _parse_cache[hash(txt)] = desc
        return desc
 
 def get_modules(since=1):
@@ -408,18 +616,22 @@ def get_context(txt):
        """Establishes the context of the cursor in the given Blender Text object
        
        Returns one of:
-         NORMAL - Cursor is in a normal context
-         SINGLE_QUOTE - Cursor is inside a single quoted string
-         DOUBLE_QUOTE - Cursor is inside a double quoted string
-         COMMENT - Cursor is inside a comment
+         CTX_NORMAL - Cursor is in a normal context
+         CTX_SINGLE_QUOTE - Cursor is inside a single quoted string
+         CTX_DOUBLE_QUOTE - Cursor is inside a double quoted string
+         CTX_COMMENT - Cursor is inside a comment
        
        """
        
        l, cursor = txt.getCursorPos()
-       lines = txt.asLines()[:l+1]
+       lines = txt.asLines(0, l+1)
+       
+       # FIXME: This method is too slow in large files for it to be called as often
+       # as it is. So for lines below the 1000th line we do this... (quorn)
+       if l > 1000: return CTX_NORMAL
        
        # Detect context (in string or comment)
-       in_str = 0                      # 1-single quotes, 2-double quotes
+       in_str = CTX_NORMAL
        for line in lines:
                if l == 0:
                        end = cursor
@@ -428,25 +640,25 @@ def get_context(txt):
                        l -= 1
                
                # Comments end at new lines
-               if in_str == 3:
-                       in_str = 0
+               if in_str == CTX_COMMENT:
+                       in_str = CTX_NORMAL
                
                for i in range(end):
                        if in_str == 0:
-                               if line[i] == "'": in_str = 1
-                               elif line[i] == '"': in_str = 2
-                               elif line[i] == '#': in_str = 3
+                               if line[i] == "'": in_str = CTX_SINGLE_QUOTE
+                               elif line[i] == '"': in_str = CTX_DOUBLE_QUOTE
+                               elif line[i] == '#': in_str = CTX_COMMENT
                        else:
-                               if in_str == 1:
+                               if in_str == CTX_SINGLE_QUOTE:
                                        if line[i] == "'":
-                                               in_str = 0
+                                               in_str = CTX_NORMAL
                                                # In again if ' escaped, out again if \ escaped, and so on
                                                for a in range(i-1, -1, -1):
                                                        if line[a] == '\\': in_str = 1-in_str
                                                        else: break
-                               elif in_str == 2:
+                               elif in_str == CTX_DOUBLE_QUOTE:
                                        if line[i] == '"':
-                                               in_str = 0
+                                               in_str = CTX_NORMAL
                                                # In again if " escaped, out again if \ escaped, and so on
                                                for a in range(i-1, -1, -1):
                                                        if line[i-a] == '\\': in_str = 2-in_str
@@ -460,7 +672,7 @@ def current_line(txt):
        cursor).
        """
        
-       (lineindex, cursor) = txt.getCursorPos()
+       lineindex, cursor = txt.getCursorPos()
        lines = txt.asLines()
        line = lines[lineindex]
        
@@ -487,13 +699,11 @@ def get_targets(line, cursor):
        returns them as a list in the same order.
        """
        
-       targets = []
        i = cursor - 1
        while i >= 0 and (line[i].isalnum() or line[i] == '_' or line[i] == '.'):
                i -= 1
        
-       pre = line[i+1:cursor]
-       return pre.split('.')
+       return line[i+1:cursor].split('.')
 
 def get_defs(txt):
        """Returns a dictionary which maps definition names in the source code to
@@ -548,12 +758,13 @@ def print_cache_for(txt, period=sys.maxint):
        print 'Name:', desc.name, '('+str(hash(txt))+')'
        print '------------------------------------------------'
        print 'Defs:'
-       for name, params in desc.defs.items():
-               print ' ', name, params
+       for name, ddesc in desc.defs.items():
+               print ' ', name, ddesc.params, ddesc.lineno
+               print '   ', ddesc.doc
        print '------------------------------------------------'
        print 'Vars:'
-       for name in desc.vars.keys():
-               print ' ', name
+       for name, vdesc in desc.vars.items():
+               print ' ', name, vdesc.type, vdesc.lineno
        print '------------------------------------------------'
        print 'Imports:'
        for name, item in desc.imports.items():
@@ -563,13 +774,15 @@ def print_cache_for(txt, period=sys.maxint):
        for clsnme, clsdsc in desc.classes.items():
                print '  *********************************'
                print '  Name:', clsnme
+               print ' ', clsdsc.doc
                print '  ---------------------------------'
                print '  Defs:'
-               for name, params in clsdsc.defs.items():
-                       print '   ', name, params
+               for name, ddesc in clsdsc.defs.items():
+                       print '   ', name, ddesc.params, ddesc.lineno
+                       print '     ', ddesc.doc
                print '  ---------------------------------'
                print '  Vars:'
-               for name in clsdsc.vars.keys():
-                       print '   ', name
+               for name, vdesc in clsdsc.vars.items():
+                       print '   ', name, vdesc.type, vdesc.lineno
                print '  *********************************'
        print '================================================'