Commits

Henning Schröder committed 4481bad Merge

merge

Comments (0)

Files changed (7)

simple-python-checker.py

+# From http://code.activestate.com/recipes/578023-simple-python-checker/
+"""
+    @author    Thomas Lehmann
+    @file      simple-python-checker.py
+    @language  pypy 1.7 and python 3.2 have been used
+
+    @note Using the tool against itself should never throw any warning or error!
+    @note I don't use TAB's and indentation is 4.
+    @note I'm using the documentation tool 'epydoc' (http://epydoc.sourceforge.net/)
+          for generating HTML documentation of this.
+
+    @todo verifying that given file is a python file
+    @todo allow first parameter for tool to be a folder/path instead of a file.
+    @todo new limit: relation of code/comments per file
+    @todo new limit: relation of code/comments per class
+    @todo new limit: relation of code/comments per function/method
+
+    @see <a href="http://en.wikipedia.org/wiki/Source_lines_of_code">
+          http://en.wikipedia.org/wiki/Source_lines_of_code</a>
+    @see <a href="http://www.dwheeler.com/sloccount/sloccount.html">
+          http://www.dwheeler.com/sloccount/sloccount.html</a>
+"""
+__docformat__ = "javadoc en"
+
+import sys
+import os
+import ast
+
+class GreenYellowRedLimit:
+    """ this class handles three ranges: green, yellow and red.
+        <ul>
+            <li><b>green</b>:  indicates that a value is valid
+            <li><b>yellow</b>: indicates that the value is accepted but not ok.
+                               A warning message will be generated.
+            <li><b>red</b>:    indicates an unacceptable state.
+                               An error message will be generated.
+        </ul>
+    """
+    def __init__(self, maxGreen, maxYellow, message):
+        """ stores limit information
+            @param maxGreen
+                        is the maximum value which is valid
+            @param maxYellow
+                        is the maximum value for warning. Is the value more
+                        then it is an error.
+            @param message
+                        is the message printed when the limit is at least
+                        more than maxGreen.
+        """
+        self.maxGreen  = maxGreen
+        self.maxYellow = maxYellow
+        self.message   = message
+
+    def isGreenFor(self, value):
+        """ Checking for a value to be in a valid range.
+            @param  value
+                        that value to check to be in green state (valid)
+            @return true when the given value is valid.
+        """
+        return value <= self.maxGreen
+
+    def isRedFor(self, value):
+        """ Checking for a value to be in an invalid range.
+            @param  value
+                        that value to check to be in red state (invalid)
+            @return true when the given value is invalid.
+        """
+        return value > self.maxYellow
+
+    def getMessage(self, pathAndFileName, lineNr, value):
+        """ generates printable warning/error message
+            @param pathAndFileName
+                    path and filename of the pyton file
+            @param lineNr
+                    that line the warning/error refers to
+            @param value
+                    that value that raises the warning/error
+
+            @note It is assumed that the "not green" status has
+                  been checked before.
+        """
+        prompt = "%s" % pathAndFileName
+
+        if lineNr >= 0: prompt += "(%d): " % lineNr
+        else:           prompt += "(1): "
+
+        if value > self.maxYellow: prompt += "error: "
+        else:                      prompt += "warning: "
+
+        return prompt + self.message + ", value is %d" % value + \
+               " (green<=%d,yellow>=%d,red>=%d)" % \
+               (self.maxGreen, self.maxGreen+1, self.maxYellow+1)
+
+class Limits:
+    """ Does group all limits to be checked by this scripts """
+    def __init__(self):
+        """ initializes container for limit defintions only """
+        self.definitions = {}
+
+    def registerLimit(self, name, limit):
+        """ registration of a limit
+            @param name
+                    name of the limit
+            @param limit
+                    instance of GreenYellowRedLimit class
+        """
+        if not name in self.definitions:
+            self.definitions[name] = limit
+        return limit
+
+    def maxAttributesPerClass(self):
+        """ <b>Too many attributes per class</b>:<br>
+            Is an indicator for bad design. Maybe the class is too complex or an
+            improper way to store information has been used. """
+        return self.registerLimit(self.maxAttributesPerClass.__name__, \
+            GreenYellowRedLimit( 10,  15, "too many attributes in class"))
+
+    def maxFunctionsPerClass(self):
+        """ <b>Too many functions per class</b>:<br>
+            Is an indicator for bad design. Maybe the class does handle too much. """
+        return self.registerLimit(self.maxFunctionsPerClass.__name__, \
+            GreenYellowRedLimit( 15,  20, "too many functions in class"))
+
+    def maxFunctionsPerFile(self):
+        """ <b>Too many functions per file</b>:<br>
+            Is an indicator for bad design. You have too many logic in one file.
+            The different to many functions per class is that global functions are
+            counted as well as functions of multiple classes in same file. """
+        return self.registerLimit(self.maxFunctionsPerFile.__name__, \
+            GreenYellowRedLimit( 35,  40, "too many functions in file"))
+
+    def maxClassesPerFile(self):
+        """ <b>Too many classes per file</b>:<br>
+            Is an indicator for bad design and/or simply the fact that the file
+            could be splitted up for different classes. """
+        return self.registerLimit(self.maxClassesPerFile.__name__, \
+            GreenYellowRedLimit(  4,   6, "too many classes in file"))
+
+    def maxParametersPerFunction(self):
+        """ <b>Too many parameters in function/method</b>:<br>
+            Is an indicator for bad design. Are many of those parameters also required
+            by other functions or methods? Can't you provide a class or a dictionary? """
+        return self.registerLimit(self.maxParametersPerFunction.__name__, \
+            GreenYellowRedLimit(  3,   5, "too many parameters in function/method"))
+
+    def maxLinesPerFunction(self):
+        """ <b>Too many lines per function/method</b>:<br>
+            The idea is to have - more or less - the content of the whole function
+            or method visible on one screen to avoid scrolling. On Windows I have
+            a "Courier New" font with size 10 and that's really not too big and not too
+            small; with this I can see about 50 lines of code. With an output window or
+            other information at the bottom (IDE) you will see less than this. This metric
+            includes comments, excludes blanks."""
+        return self.registerLimit(self.maxLinesPerFunction.__name__, \
+            GreenYellowRedLimit( 50, 100, "too many lines in function/method"))
+
+    def maxControlStatementsPerFunction(self):
+        """ <b>Too many control statements</b>:<br>
+            Is an indicator that the function/method is too complex. """
+        return self.registerLimit(self.maxControlStatementsPerFunction.__name__, \
+            GreenYellowRedLimit( 15,  20, "too many control statements"))
+
+    def maxCharactersPerLine(self):
+        """ <b>Line too long</b>:<br>
+            I want to see the whole line without scrolling. Another aspect of this
+            is when you use tools for comparing two versions (side by side) or
+            when printing it out; it's to avoid wrapping code into the next lines.
+            <b>It's about readability!</b> """
+        return self.registerLimit(self.maxCharactersPerLine.__name__, \
+            GreenYellowRedLimit( 95, 110, "line too long"))
+
+    def maxLinesPerFile(self):
+        """ <b>Too many lines in file</b>:<br>
+            Is an indicator for bad design. I know from files with several thousand
+            lines of code and especially those file mostly are difficult to maintain.
+            This metric includes comments, excludes blank lines. """
+        return self.registerLimit(self.maxLinesPerFile.__name__, \
+            GreenYellowRedLimit(550, 750, "too many lines in file"))
+
+    def maxIndentationLevel(self):
+        """ <b>Too many indentation levels</b>:<br>
+            Is an indicator that the code is too complex! It's about examples like having
+            an if statement containing an if statement and again containing an if
+            statement and again ... """
+        return self.registerLimit(self.maxIndentationLevel.__name__, \
+            GreenYellowRedLimit(  3,   5, "too many indentation levels"))
+
+    def maxTabs(self):
+        """ <b>Too many tabs</b>:<br>
+            This a visual style issue. You can disallow tabs if wanted.
+            Default: no tabs are allowed. """
+        return self.registerLimit(self.maxTabs.__name__, \
+            GreenYellowRedLimit(  0,   0, "too many tabs"))
+
+FILE     = 0
+CLASS    = 1
+FUNCTION = 2
+LINENR   = 3
+LEVEL    = 4
+
+class FileData:
+    """ represent all information for one file from one analyse """
+    def __init__(self, pathAndFileName):
+        """ initializes members for one file analyse """
+        self.pathAndFileName = pathAndFileName
+        self.classes         = {}
+        self.attributes      = {}
+        self.functions       = {}
+
+        self.errors          = 0
+        self.warnings        = 0
+        self.totalLines      = 0
+        self.totalBlankLines = 0
+        self.messages        = []
+
+class SimplePythonChecker(ast.NodeVisitor):
+    """ As visitor on one side it can traverse the python code
+        for checking structural things. Comments, length of lines,
+        indentation depth are topic which need to be handled separately.
+
+        @note The tool does not check whether somebody creates attributes
+              outside of the __init__ method.
+    """
+    def __init__(self):
+        """ initializing to have some empty containers and the limits """
+        self.limits     = Limits()
+        self.current    = {LEVEL: 0}
+        self.files      = {}
+
+    def analyze(self, pathAndFileName):
+        """ main method for analyzing one python file
+            @param pathAndFileName
+                    path and filename of a python file to analyze"""
+        print("...analyzing %s..." % pathAndFileName)
+        # remember the current file for further processing
+        newFileData = FileData(pathAndFileName)
+        self.files[pathAndFileName] = newFileData
+        self.current[FILE]          = newFileData
+
+        # the content of the whole file
+        code = open(pathAndFileName).read()
+        # collecting relevant information for limit checking walking the AST tree.
+        tree = ast.parse(code)
+        self.visit(tree)
+        # line based analyse
+        self.analyzeCode(code)
+        # checking collected information
+        self.checkLimits()
+        self.finalize()
+
+    def analyzeCode(self, code):
+        """ line based analyse.
+            @param code
+                    the content of the whole file (usually) """
+        currentFile = self.current[FILE]
+
+        # checking all line lengths for given file (code)
+        lineNr       = 1
+        for line in code.split('\n'):
+            # checking for line length
+            limitToCheck = self.limits.maxCharactersPerLine()
+            value        = len(line)
+            self.checkLimit(lineNr, limitToCheck, value)
+
+            # checking for tabs in line
+            limitToCheck = self.limits.maxTabs()
+            value        = line.count("\t")
+            self.checkLimit(lineNr, limitToCheck, value)
+
+            # counting blank lines
+            if len(line.strip()) == 0:
+                currentFile.totalBlankLines += 1
+                functions = [ function for function in currentFile.functions.values() \
+                              if function["lineNr"]                   < lineNr and \
+                                 function["lineNr"]+function["lines"] > lineNr ]
+
+                if len(functions) == 1:
+                    functions[0]["blankLines"] += 1
+
+            lineNr += 1
+
+        # checking number of lines in given file (code)
+        limitToCheck = self.limits.maxLinesPerFile()
+        lineNr       = 1
+        value        = code.count("\n") + 1
+        self.checkLimit(lineNr, limitToCheck, value - currentFile.totalBlankLines)
+
+        currentFile.totalLines = value
+
+    def visit_ClassDef(self, node):
+        """ stores the class.
+            @param node
+                    walking the AST tree a class definition has been found
+        """
+        currentFile = self.current[FILE]
+
+        newClass               = {}
+        newClass["lineNr"]     = node.lineno
+        newClass["attributes"] = set()
+        newClass["functions"]  = set()
+
+        currentFile.classes[node.name] = newClass
+        self.current[CLASS] = node.name
+        self.generic_visit(node)
+
+    def visit_Attribute(self, node):
+        """ stores attributes and counts them class wise
+            @param node
+                walking the AST tree an attribute has been found. In python you
+                cannot declare so I decide that the c'tor (__init__) has to initialize
+                all valid members and only those count for the limit checking
+        """
+        currentFile = self.current[FILE]
+
+        if FUNCTION in self.current and self.current[FUNCTION] == "__init__":
+            newAttribute           = {}
+            newAttribute["lineNr"] = node.lineno
+
+            # provide class information (when available)
+            if CLASS in self.current and self.current[CLASS]:
+                newAttribute["class"] = self.current[CLASS]
+                currentFile.classes[self.current[CLASS]]["attributes"].add(node.attr)
+
+            currentFile.attributes[node.attr] = newAttribute
+        self.generic_visit(node)
+
+    def visit_FunctionDef(self, node):
+        """ stores function and register it at class if it's a method.
+            @param node
+                walking the AST tree a function/method definition has been found.
+                It's to check for too many parameters, for too many lines in
+                a function/method and for complexity.
+        """
+        currentFile = self.current[FILE]
+
+        newFunction               = {}
+        newFunction["lineNr"]     = node.lineno
+        newFunction["cstms"]      = 0
+        newFunction["blankLines"] = 0
+
+        # this is because of handling different python versions...
+        try:    newFunction["argumentNames"] = [arg.arg for arg in node.args.args]
+        except: newFunction["argumentNames"] = [arg.id for arg in node.args.args]
+
+        # provide class information (when available)
+        if CLASS in self.current and self.current[CLASS]:
+            currentClass = currentFile.classes[self.current[CLASS]]
+            if node.lineno <= currentClass["lineNr"]:
+                self.current[CLASS] = None
+            else:
+                newFunction["class"] = self.current[CLASS]
+                currentClass["functions"].add(node.name)
+
+        currentFile.functions[node.name] = newFunction
+        self.current[FUNCTION] = node.name
+        self.generic_visit(node)
+        # calcuates number of lines of function/method
+        newFunction["lines"] = self.current[LINENR] - node.lineno
+        self.current[FUNCTION]  = None
+
+    def visit_If(self, node):
+        """ required to check for indentation level and complexity """
+        currentFile = self.current[FILE]
+        self.current[LEVEL] += 1
+
+        limitToCheck = self.limits.maxIndentationLevel()
+        lineNr       = node.lineno
+        value        = self.current[LEVEL]
+
+        self.checkLimit(lineNr, limitToCheck, value)
+
+        if self.current[FUNCTION] in currentFile.functions:
+            currentFunction = currentFile.functions[self.current[FUNCTION]]
+            currentFunction["cstms"] += 1
+
+        self.generic_visit(node)
+        self.current[LEVEL] -= 1
+
+    def visit_For(self, node):
+        """ required to check for indentation level and complexity
+            @param node
+                This node represents an 'if' statement (if, elif and else)
+        """
+        currentFile = self.current[FILE]
+        self.current[LEVEL] += 1
+
+        limitToCheck = self.limits.maxIndentationLevel()
+        lineNr       = node.lineno
+        value        = self.current[LEVEL]
+
+        self.checkLimit(lineNr, limitToCheck, value)
+
+        if self.current[FUNCTION] in currentFile.functions:
+            currentFunction = currentFile.functions[self.current[FUNCTION]]
+            currentFunction["cstms"] += 1
+
+        self.generic_visit(node)
+        self.current[LEVEL] -= 1
+
+    def visit_While(self, node):
+        """ required to check for indentation level and complexity """
+        currentFile = self.current[FILE]
+
+        self.current[LEVEL] += 1
+
+        limitToCheck = self.limits.maxIndentationLevel()
+        lineNr       = node.lineno
+        value        = self.current[LEVEL]
+
+        self.checkLimit(lineNr, limitToCheck, value)
+
+        if self.current[FUNCTION] in currentFile.functions:
+            currentFunction = currentFile.functions[self.current[FUNCTION]]
+            currentFunction["cstms"] += 1
+
+        self.generic_visit(node)
+        self.current[LEVEL] -= 1
+
+    def visit_Return(self, node):
+        """ required to check for complexity """
+        currentFile = self.current[FILE]
+
+        if self.current[FUNCTION] in currentFile.functions:
+            currentFunction = currentFile.functions[self.current[FUNCTION]]
+            currentFunction["cstms"] += 1
+
+        self.generic_visit(node)
+
+    def checkLimits(self):
+        """ checking different limits """
+        currentFile = self.current[FILE]
+
+        for className in currentFile.classes:
+            # checking for number of attributes/members per class
+            limitToCheck = self.limits.maxAttributesPerClass()
+            value        = len(currentFile.classes[className]["attributes"])
+            lineNr       = currentFile.classes[className]["lineNr"]
+            self.checkLimit(lineNr, limitToCheck, value)
+
+            # checking for number of functions/methods per class
+            limitToCheck = self.limits.maxFunctionsPerClass()
+            value        = len(currentFile.classes[className]["functions"])
+            self.checkLimit(lineNr, limitToCheck, value)
+
+        for functionName in currentFile.functions:
+            # checking for number of parameters per function/method
+            limitToCheck = self.limits.maxParametersPerFunction()
+            lineNr       = currentFile.functions[functionName]["lineNr"]
+            value        = len(currentFile.functions[functionName]["argumentNames"])
+
+            if "self" in currentFile.functions[functionName]["argumentNames"]:
+                value -= 1
+
+            self.checkLimit(lineNr, limitToCheck, value)
+
+            # checking for number of lines per function/method
+            limitToCheck = self.limits.maxLinesPerFunction()
+            value        = currentFile.functions[functionName]["lines"]
+            value       -= currentFile.functions[functionName]["blankLines"]
+            self.checkLimit(lineNr, limitToCheck, value)
+            # checking for number of control statements per function/method
+            limitToCheck = self.limits.maxControlStatementsPerFunction()
+            value        = currentFile.functions[functionName]["cstms"]
+            self.checkLimit(lineNr, limitToCheck, value)
+
+
+        # checking number of functions/methods per file
+        limitToCheck = self.limits.maxFunctionsPerFile()
+        value        = len(currentFile.functions)
+        lineNr       = 1
+        self.checkLimit(lineNr, limitToCheck, value)
+        # checking number of classes per file
+        limitToCheck = self.limits.maxClassesPerFile()
+        value        = len(currentFile.classes)
+        self.checkLimit(lineNr, limitToCheck, value)
+
+    def checkLimit(self, lineNr, limitToCheck, value):
+        """ checks for a single limit and prints a message
+            if given value is not valid """
+        currentFile = self.current[FILE]
+
+        if not limitToCheck.isGreenFor(value):
+            message = limitToCheck.getMessage(currentFile.pathAndFileName, lineNr, value)
+            currentFile.messages.append((lineNr, message))
+            if limitToCheck.isRedFor(value): currentFile.errors   += 1
+            else:                            currentFile.warnings += 1
+
+    def generic_visit(self, node):
+        """ main reason for overwriting this method is to be able to
+            calculate how many line are in a block (function/method/class/...) """
+        try:    self.current[LINENR] = node.lineno
+        except: pass
+        # traverse further nodes
+        ast.NodeVisitor.generic_visit(self, node)
+
+    def finalize(self):
+        """ printing some final information from last analyse """
+        currentFile = self.current[FILE]
+
+        for message in sorted(currentFile.messages):
+            print(message[1])
+
+        functions = [name for name in currentFile.functions \
+                     if len(currentFile.functions[name]["argumentNames"]) == 0 or \
+                        not currentFile.functions[name]["argumentNames"][0] == 'self']
+
+        print("...%3d lines processed (with %d blank lines)" \
+              % (currentFile.totalLines, currentFile.totalBlankLines))
+        print("...%3d function(s) processed" % len(functions))
+        print("...%3d method(s) processed" % (len(currentFile.functions) - len(functions)))
+        print("...%3d warning(s) - %d error(s)" \
+              % (currentFile.warnings, currentFile.errors))
+
+def main():
+    """ script can be executed with filename as parameter, otherwise the
+        script itself will be checked """
+    print("Simple Python Checker v0.2 by Thomas Lehmann")
+    print("...running Python %s" % sys.version.replace("\n", " - "))
+    checker = SimplePythonChecker()
+    if len(sys.argv) == 2:
+        pathAndFileName = sys.argv[1].strip()
+        if os.path.isfile(pathAndFileName):
+            checker.analyze(pathAndFileName)
+        else:
+            print("...error: file '%s' does not exist" % pathAndFileName)
+    else:
+        checker.analyze("simple-python-checker.py")
+
+if __name__ == "__main__":
+    main()

vimftpplugin/README

+From https://github.com/tarmack/vim-python-ftplugin
+
+This software is licensed under the MIT license.
+(c) 2011 Peter Odding <peter@peterodding.com> and Bart Kroon <bart@tarmack.eu>.

vimftpplugin/example.py

+global_var = 42
+var1, var2 = 1, 2
+some_string = ''
+shadowed = ''
+
+class ExampleClass:
+
+  def __init__(self, init_arg, kw=[], kw_indirect=some_string):
+    shadowed = []
+    print global_var, shadowed, kw_indirect, kw
+    if init_arg:
+      def fun_inside_if(): pass
+    elif 1:
+      def fun_inside_elif_1(): pass
+    elif 2:
+      def fun_inside_elif_2(): pass
+    else:
+      def fun_inside_else(): pass
+
+  def a(self, example_argument):
+    for i in xrange(5):
+      print example_argument
+    while True:
+      def inside_while(): pass
+
+  def b(self):
+    def nested():
+      print 5
+    nested()
+
+ExampleClass.b(normal_arg, kw_arg=5, *(1, 2), **{'key': 42})
+ExampleClass.a('')
+
+def newlist():
+  return []
+
+l = newlist()
+
+def newmaps():
+  return set(), dict()
+
+s, d = newmaps()
+
+if True:
+  variant = []
+else:
+  variant = ''
+print variant
+
+def func(blaat):
+  print dir(s2)
+
+func(set())
+func(list())

vimftpplugin/inference.py

+# Type inference engine for the Python file type plug-in for Vim.
+# Authors:
+#  - Peter Odding <peter@peterodding.com>
+#  - Bart Kroon <bart@tarmack.eu>
+# Last Change: October 9, 2011
+# URL: https://github.com/tarmack/vim-python-ftplugin
+
+# TODO Nested yields don't work in Python, are there any nice alternatives? (I miss Lua's coroutines)
+# http://groups.google.com/group/comp.lang.python/browse_frm/thread/fcd2709952d23e34?hl=en&lr=&ie=UTF-8&rnum=9&prev=/&frame=on
+
+import ast
+import collections
+
+DEBUG = False
+LOGFILE = '/tmp/inference.log'
+
+# Mapping of built-ins to result types.
+BUILTINS = {
+    'bool': bool,
+    'int': int,
+    'long': long,
+    'float': float,
+    'str': str,
+    'unicode': unicode,
+    'list': list,
+    'dict': dict,
+    'set': set,
+    'frozenset': frozenset,
+    'object': object,
+    'tuple': tuple,
+    'file': file,
+    'open': file,
+    'len': int,
+    'locals': dict,
+    'max': int,
+    'min': int,
+}
+
+# Mapping of AST types to constructors.
+AST_TYPES = {
+    id(ast.Num): int,
+    id(ast.Str): str,
+    id(ast.List): list,
+    id(ast.ListComp): list,
+    id(ast.Dict): dict,
+}
+
+def log(msg, *args):
+  if DEBUG:
+    with open(LOGFILE, 'a') as handle:
+      handle.write(msg % args + '\n')
+
+def complete_inferred_types():
+  import vim
+  engine = TypeInferenceEngine(vim.eval('source'))
+  line = int(vim.eval('line'))
+  column = int(vim.eval('column'))
+  for name, types in engine.complete(line, column).iteritems():
+    fields = [name]
+    for t in types:
+      fields.append(t.__name__)
+    print '|'.join(fields)
+
+class TypeInferenceEngine:
+
+  def __init__(self, source):
+    self.tree = ast.parse(source)
+    self.link_parents(self.tree)
+
+  def complete(self, line, column):
+    node = self.find_node(line, column)
+    if node:
+      candidates = collections.defaultdict(list)
+      for possible_type in self.evaluate(node):
+        for name in dir(possible_type):
+          candidates[name].append(possible_type)
+      return candidates
+
+  def link_parents(self, node):
+    ''' Decorate the AST with child -> parent references. '''
+    for child in self.get_children(node):
+      self.link_parents(child)
+      child.parent = node
+
+  def get_parents(self, node):
+    ''' Yield all parent nodes of a given AST node. '''
+    while hasattr(node, 'parent'):
+      node = node.parent
+      yield node
+
+  def get_children(self, node):
+    ''' Get the nodes directly contained in a given AST node. '''
+    nodes = []
+    if isinstance(node, ast.If):
+      # Might be nice to exclude "if False:" blocks here? :-)
+      # TODO Take isinstance() into account as a type hint.
+      nodes.append(node.test)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.Print):
+      nodes.extend(node.values)
+    elif isinstance(node, (ast.Expr, ast.Return)):
+      nodes.append(node.value)
+    elif isinstance(node, (ast.Module, ast.ClassDef)):
+      nodes.extend(node.body)
+    elif isinstance(node, ast.FunctionDef):
+      nodes.extend(node.args.args)
+      nodes.extend(node.args.defaults)
+      if node.args.vararg:
+        nodes.append(node.args.vararg)
+      if node.args.kwarg:
+        nodes.append(node.args.kwarg)
+      nodes.extend(node.body)
+    elif isinstance(node, ast.While):
+      nodes.append(node.test)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.For):
+      nodes.append(node.target)
+      nodes.append(node.iter)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.Call):
+      nodes.append(node.func)
+      nodes.extend(node.args)
+      nodes.extend(k.value for k in node.keywords)
+      if node.starargs:
+        nodes.append(node.starargs)
+      if node.kwargs:
+        nodes.append(node.kwargs)
+    elif isinstance(node, (ast.Tuple, ast.List)):
+      nodes.extend(node.elts)
+    elif isinstance(node, ast.Dict):
+      for i in xrange(len(node.values)):
+        nodes.append(node.keys[i])
+        nodes.append(node.values[i])
+    elif isinstance(node, ast.Assign):
+      nodes.extend(node.targets)
+      nodes.append(node.value)
+    elif isinstance(node, ast.Attribute):
+      nodes.append(node.value)
+      #nodes.append(node.attr)
+    elif isinstance(node, ast.Import):
+      #for imp in node.names:
+      #  nodes.append(imp.asname or imp.name)
+      pass
+    elif isinstance(node, ast.ImportFrom):
+      log("ImportFrom: %s", dir(node))
+      node
+    elif isinstance(node, (ast.Num, ast.Str, ast.Name, ast.Pass)):
+      # terminals
+      pass
+    else:
+      assert False, 'Node %s (%s) unsupported' % (node, type(node))
+    return nodes
+
+  def find_node(self, lnum, column):
+    ''' Find the node at the given (line, column) in the AST. '''
+    for node in ast.walk(self.tree):
+      node_id = getattr(node, 'id', '')
+      node_lnum = getattr(node, 'lineno', 0)
+      node_col = getattr(node, 'col_offset', 0)
+      if node_lnum == lnum and node_col <= column <= node_col + len(node_id):
+        return node
+
+  def evaluate(self, node):
+    ''' Resolve an AST expression node to its primitive value(s). '''
+    key = id(type(node))
+    if key in AST_TYPES:
+      # Constructor with special syntax (0, '', [], {}).
+      yield AST_TYPES[key]
+    elif type(node) == type:
+      yield node
+    elif isinstance(node, ast.Call):
+      # Function call.
+      # FIXME node.func can be an ast.Attribute!
+      name = getattr(node.func, 'id', None)
+      if name in BUILTINS:
+        # Call to built-in (int(), str(), len(), max(), etc).
+        yield BUILTINS[name]
+      # Search return type(s) of user defined function(s).
+      for func in self.find_function_definitions(node):
+        for n in ast.walk(func):
+          if isinstance(n, ast.Return):
+            for result in self.evaluate(n.value):
+              yield result
+    elif isinstance(node, (ast.Tuple, ast.List)):
+      if node.elts:
+        for value in node.elts:
+          yield value
+      elif isinstance(node, ast.Tuple):
+        yield tuple
+      elif isinstance(node, ast.List):
+        yield list
+    elif isinstance(node, ast.Name):
+      # Evaluate variable reference.
+      for parent, kind, location in self.resolve(node):
+        if isinstance(parent, ast.Assign):
+          # Variable has been assigned.
+          if len(parent.targets) > 1:
+            # FIXME Prone to breaking on nested structures.. Use a path expression instead!
+            values = self.flatten(parent.value, [])
+          else:
+            values = [parent.value]
+          # Evaluate stand alone function call before unpacking?
+          if len(values) == 1 and isinstance(values[0], ast.Call):
+            values = list(self.evaluate(values[0]))
+          for result in self.evaluate(values[location]):
+            yield result
+        elif kind == 'pos':
+          # Evaluate function argument default value?
+          num_required = len(parent.args.args) - len(parent.args.defaults)
+          if location >= num_required:
+            for result in self.evaluate(parent.args.defaults[location - num_required]):
+              yield result
+          # Check function arguments at potential call sites.
+          for call in self.find_function_calls(parent):
+            for result in self.evaluate(call.args[location]):
+              yield result
+        elif kind == 'var':
+          yield tuple
+        elif kind == 'kw':
+          yield dict
+        else:
+          assert False
+
+  def find_function_calls(self, node):
+    ''' Yield the function/method calls that might be related to a node. '''
+    assert isinstance(node, ast.FunctionDef)
+    for n in ast.walk(self.tree):
+      if isinstance(n, ast.Call) and self.call_to_name(n) == node.name:
+        yield n
+
+  def find_function_definitions(self, node):
+    ''' Yield the function definitions that might be related to a node. '''
+    assert isinstance(node, ast.Call)
+    name = self.call_to_name(node)
+    for n in ast.walk(self.tree):
+      if isinstance(n, ast.FunctionDef) and n.name == name:
+        yield n
+
+  def call_to_name(self, node):
+    ''' Translate a call to a function/method name. '''
+    assert isinstance(node, ast.Call)
+    if isinstance(node.func, ast.Name):
+      return node.func.id
+    elif isinstance(node.func, ast.Attribute):
+      return node.func.attr
+    else:
+      assert False
+
+  def resolve(self, node):
+    ''' Resolve an ast.Name node to its definition(s). '''
+    # TODO Class and function definitions are assignments as well!
+    # TODO Import statements are assignments as well!
+    sources = set()
+    assert isinstance(node, ast.Name)
+    for parent in self.get_parents(node):
+      # Search for variable assignments in the current scope?
+      if isinstance(parent, (ast.Module, ast.FunctionDef)):
+        for n in ast.walk(parent):
+          if isinstance(n, ast.Assign):
+            for i, target in enumerate(self.flatten(n.targets, [])):
+              if target.id == node.id:
+                sources.add((n, 'asn', i))
+      # Search function arguments?
+      if isinstance(parent, ast.FunctionDef):
+        # Check the named positional arguments.
+        for i, argument in enumerate(parent.args.args):
+          if argument.id == node.id:
+            sources.add((parent, 'pos', i))
+        # Check the tuple with other positional arguments.
+        if parent.args.vararg and parent.args.vararg.id == node.id:
+          sources.add((parent, 'var', parent.args.vararg))
+        # Check the dictionary with other keyword arguments.
+        if parent.args.kwarg and parent.args.kwarg.id == node.id:
+          sources.add(parent, 'kw', parent.args.kwarg)
+    return sources
+
+  def flatten(self, nested, flat):
+    ''' Squash a nested sequence into a flat list of nodes. '''
+    if isinstance(nested, (ast.Tuple, ast.List)):
+      for node in nested.elts:
+        self.flatten(node, flat)
+    elif isinstance(nested, (tuple, list)):
+      for node in nested:
+        self.flatten(node, flat)
+    else:
+      flat.append(nested)
+    return flat
+
+  ########################
+  ## Debugging helpers. ##
+  ########################
+
+  def dump(self, node, level=0):
+    ''' Print an AST subtree as a multi line indented string. '''
+    if isinstance(node, list):
+        for n in node:
+            self.dump(n, level)
+        return
+    if not isinstance(node, ast.Expr):
+      print '  ' * level + '(' + type(node).__name__ + ') ' + self.format(node)
+      level += 1
+    for child in self.get_children(node):
+      self.dump(child, level)
+
+  def format(self, node):
+    ''' Format an AST node as a string. '''
+    # Blocks.
+    if isinstance(node, ast.Module):
+      return 'module'
+    elif isinstance(node, ast.ClassDef):
+      return 'class ' + node.name
+    elif isinstance(node, ast.FunctionDef):
+      return 'function ' + node.name
+    elif isinstance(node, ast.For):
+      return 'for'
+    elif isinstance(node, ast.While):
+      return 'while'
+    elif isinstance(node, ast.If):
+      return 'if'
+    elif isinstance(node, ast.Pass):
+      return 'pass'
+    elif isinstance(node, ast.Print):
+      return 'print'
+    elif isinstance(node, ast.Assign):
+      return '%s=%s' % (', '.join(self.format(t) for t in node.targets), self.format(node.value))
+    # Expressions.
+    elif isinstance(node, ast.Call):
+      args = [self.format(a) for a in node.args]
+      for keyword in node.keywords:
+        args.append(keyword.arg + '=' + self.format(keyword.value))
+      if node.starargs:
+        args.append('*' + self.format(node.starargs))
+      if node.kwargs:
+        args.append('**' + self.format(node.kwargs))
+      name = self.format(node.func)
+      return 'call %s(%s)' % (name, ', '.join(args))
+    elif isinstance(node, ast.Tuple):
+      elts = ', '.join(self.format(e) for e in node.elts)
+      return '(' + elts + ')'
+    elif isinstance(node, ast.List):
+      elts = ', '.join(self.format(e) for e in node.elts)
+      return '[' + elts + ']'
+    elif isinstance(node, ast.Dict):
+      pairs = []
+      for i in xrange(len(node.values)):
+        key = self.format(node.keys[i])
+        value = self.format(node.values[i])
+        pairs.append(key + ': ' + value)
+      return '{' + ', '.join(pairs) + '}'
+    elif isinstance(node, ast.Name):
+      return node.id
+    elif isinstance(node, ast.Attribute):
+      return self.format(node.value) + '.' + self.format(node.attr)
+    elif isinstance(node, ast.Num):
+      return str(node.n)
+    elif isinstance(node, ast.Str):
+      return "'%s'" % node.s
+    elif isinstance(node, ast.Expr):
+      return self.format(node.value)
+    elif isinstance(node, (str, unicode)):
+      return node
+    elif isinstance(node, collections.Iterable):
+      values = ', '.join(self.format(v) for v in node)
+      if isinstance(node, tuple): values = '(%s)' % values
+      if isinstance(node, list): values = '[%s]' % values
+      return values
+    else:
+      return str(node)

vimftpplugin/other.py

+# from https://github.com/vim-scripts/pythoncomplete/tree/master/ftplugin
+import sys, tokenize, cStringIO, types
+from token import NAME, DEDENT, NEWLINE, STRING
+
+debugstmts=[]
+def dbg(s): debugstmts.append(s)
+def showdbg():
+    for d in debugstmts: print "DBG: %s " % d
+
+def vimcomplete(context,match):
+    global debugstmts
+    debugstmts = []
+    try:
+        import vim
+        def complsort(x,y):
+            try:
+                xa = x['abbr']
+                ya = y['abbr']
+                if xa[0] == '_':
+                    if xa[1] == '_' and ya[0:2] == '__':
+                        return xa > ya
+                    elif ya[0:2] == '__':
+                        return -1
+                    elif y[0] == '_':
+                        return xa > ya
+                    else:
+                        return 1
+                elif ya[0] == '_':
+                    return -1
+                else:
+                   return xa > ya
+            except:
+                return 0
+        cmpl = Completer()
+        cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
+        all = cmpl.get_completions(context,match)
+        all.sort(complsort)
+        dictstr = '['
+        # have to do this for double quoting
+        for cmpl in all:
+            dictstr += '{'
+            for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x])
+            dictstr += '"icase":0},'
+        if dictstr[-1] == ',': dictstr = dictstr[:-1]
+        dictstr += ']'
+        #dbg("dict: %s" % dictstr)
+        vim.command("silent let g:pythoncomplete_completions = %s" % dictstr)
+        #dbg("Completion dict:\n%s" % all)
+    except vim.error:
+        dbg("VIM Error: %s" % vim.error)
+
+class Completer(object):
+    def __init__(self):
+       self.compldict = {}
+       self.parser = PyParser()
+
+    def evalsource(self,text,line=0):
+        sc = self.parser.parse(text,line)
+        src = sc.get_code()
+        dbg("source: %s" % src)
+        try: exec(src) in self.compldict
+        except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
+        for l in sc.locals:
+            try: exec(l) in self.compldict
+            except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l))
+
+    def _cleanstr(self,doc):
+        return doc.replace('"',' ').replace("'",' ')
+
+    def get_arguments(self,func_obj):
+        def _ctor(obj):
+            try: return class_ob.__init__.im_func
+            except AttributeError:
+                for base in class_ob.__bases__:
+                    rc = _find_constructor(base)
+                    if rc is not None: return rc
+            return None
+
+        arg_offset = 1
+        if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
+        elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
+        else: arg_offset = 0
+        
+        arg_text=''
+        if type(func_obj) in [types.FunctionType, types.LambdaType]:
+            try:
+                cd = func_obj.func_code
+                real_args = cd.co_varnames[arg_offset:cd.co_argcount]
+                defaults = func_obj.func_defaults or ''
+                defaults = map(lambda name: "=%s" % name, defaults)
+                defaults = [""] * (len(real_args)-len(defaults)) + defaults
+                items = map(lambda a,d: a+d, real_args, defaults)
+                if func_obj.func_code.co_flags & 0x4:
+                    items.append("...")
+                if func_obj.func_code.co_flags & 0x8:
+                    items.append("***")
+                arg_text = (','.join(items)) + ')'
+
+            except:
+                dbg("arg completion: %s: %s" % (sys.exc_info()[0],sys.exc_info()[1]))
+                pass
+        if len(arg_text) == 0:
+            # The doc string sometimes contains the function signature
+            #  this works for alot of C modules that are part of the
+            #  standard library
+            doc = func_obj.__doc__
+            if doc:
+                doc = doc.lstrip()
+                pos = doc.find('\n')
+                if pos > 0:
+                    sigline = doc[:pos]
+                    lidx = sigline.find('(')
+                    ridx = sigline.find(')')
+                    if lidx > 0 and ridx > 0:
+                        arg_text = sigline[lidx+1:ridx] + ')'
+        if len(arg_text) == 0: arg_text = ')'
+        return arg_text
+
+    def get_completions(self,context,match):
+        dbg("get_completions('%s','%s')" % (context,match))
+        stmt = ''
+        if context: stmt += str(context)
+        if match: stmt += str(match)
+        try:
+            result = None
+            all = {}
+            ridx = stmt.rfind('.')
+            if len(stmt) > 0 and stmt[-1] == '(':
+                result = eval(_sanitize(stmt[:-1]), self.compldict)
+                doc = result.__doc__
+                if doc is None: doc = ''
+                args = self.get_arguments(result)
+                return [{'word':self._cleanstr(args),'info':self._cleanstr(doc)}]
+            elif ridx == -1:
+                match = stmt
+                all = self.compldict
+            else:
+                match = stmt[ridx+1:]
+                stmt = _sanitize(stmt[:ridx])
+                result = eval(stmt, self.compldict)
+                all = dir(result)
+
+            dbg("completing: stmt:%s" % stmt)
+            completions = []
+
+            try: maindoc = result.__doc__
+            except: maindoc = ' '
+            if maindoc is None: maindoc = ' '
+            for m in all:
+                if m == "_PyCmplNoType": continue #this is internal
+                try:
+                    dbg('possible completion: %s' % m)
+                    if m.find(match) == 0:
+                        if result is None: inst = all[m]
+                        else: inst = getattr(result,m)
+                        try: doc = inst.__doc__
+                        except: doc = maindoc
+                        typestr = str(inst)
+                        if doc is None or doc == '': doc = maindoc
+
+                        wrd = m[len(match):]
+                        c = {'word':wrd, 'abbr':m,  'info':self._cleanstr(doc)}
+                        if "function" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
+                        elif "method" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
+                        elif "module" in typestr:
+                            c['word'] += '.'
+                        elif "class" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '('
+                        completions.append(c)
+                except:
+                    i = sys.exc_info()
+                    dbg("inner completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
+            return completions
+        except:
+            i = sys.exc_info()
+            dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
+            return []
+
+class Scope(object):
+    def __init__(self,name,indent,docstr=''):
+        self.subscopes = []
+        self.docstr = docstr
+        self.locals = []
+        self.parent = None
+        self.name = name
+        self.indent = indent
+
+    def add(self,sub):
+        #print 'push scope: [%s@%s]' % (sub.name,sub.indent)
+        sub.parent = self
+        self.subscopes.append(sub)
+        return sub
+
+    def doc(self,str):
+        """ Clean up a docstring """
+        d = str.replace('\n',' ')
+        d = d.replace('\t',' ')
+        while d.find('  ') > -1: d = d.replace('  ',' ')
+        while d[0] in '"\'\t ': d = d[1:]
+        while d[-1] in '"\'\t ': d = d[:-1]
+        dbg("Scope(%s)::docstr = %s" % (self,d))
+        self.docstr = d
+
+    def local(self,loc):
+        self._checkexisting(loc)
+        self.locals.append(loc)
+
+    def copy_decl(self,indent=0):
+        """ Copy a scope's declaration only, at the specified indent level - not local variables """
+        return Scope(self.name,indent,self.docstr)
+
+    def _checkexisting(self,test):
+        "Convienance function... keep out duplicates"
+        if test.find('=') > -1:
+            var = test.split('=')[0].strip()
+            for l in self.locals:
+                if l.find('=') > -1 and var == l.split('=')[0].strip():
+                    self.locals.remove(l)
+
+    def get_code(self):
+        str = ""
+        if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n'
+        for l in self.locals:
+            if l.startswith('import'): str += l+'\n'
+        str += 'class _PyCmplNoType:\n    def __getattr__(self,name):\n        return None\n'
+        for sub in self.subscopes:
+            str += sub.get_code()
+        for l in self.locals:
+            if not l.startswith('import'): str += l+'\n'
+
+        return str
+
+    def pop(self,indent):
+        #print 'pop scope: [%s] to [%s]' % (self.indent,indent)
+        outer = self
+        while outer.parent != None and outer.indent >= indent:
+            outer = outer.parent
+        return outer
+
+    def currentindent(self):
+        #print 'parse current indent: %s' % self.indent
+        return '    '*self.indent
+
+    def childindent(self):
+        #print 'parse child indent: [%s]' % (self.indent+1)
+        return '    '*(self.indent+1)
+
+class Class(Scope):
+    def __init__(self, name, supers, indent, docstr=''):
+        Scope.__init__(self,name,indent, docstr)
+        self.supers = supers
+    def copy_decl(self,indent=0):
+        c = Class(self.name,self.supers,indent, self.docstr)
+        for s in self.subscopes:
+            c.add(s.copy_decl(indent+1))
+        return c
+    def get_code(self):
+        str = '%sclass %s' % (self.currentindent(),self.name)
+        if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers)
+        str += ':\n'
+        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
+        if len(self.subscopes) > 0:
+            for s in self.subscopes: str += s.get_code()
+        else:
+            str += '%spass\n' % self.childindent()
+        return str
+
+
+class Function(Scope):
+    def __init__(self, name, params, indent, docstr=''):
+        Scope.__init__(self,name,indent, docstr)
+        self.params = params
+    def copy_decl(self,indent=0):
+        return Function(self.name,self.params,indent, self.docstr)
+    def get_code(self):
+        str = "%sdef %s(%s):\n" % \
+            (self.currentindent(),self.name,','.join(self.params))
+        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
+        str += "%spass\n" % self.childindent()
+        return str
+
+class PyParser:
+    def __init__(self):
+        self.top = Scope('global',0)
+        self.scope = self.top
+
+    def _parsedotname(self,pre=None):
+        #returns (dottedname, nexttoken)
+        name = []
+        if pre is None:
+            tokentype, token, indent = self.next()
+            if tokentype != NAME and token != '*':
+                return ('', token)
+        else: token = pre
+        name.append(token)
+        while True:
+            tokentype, token, indent = self.next()
+            if token != '.': break
+            tokentype, token, indent = self.next()
+            if tokentype != NAME: break
+            name.append(token)
+        return (".".join(name), token)
+
+    def _parseimportlist(self):
+        imports = []
+        while True:
+            name, token = self._parsedotname()
+            if not name: break
+            name2 = ''
+            if token == 'as': name2, token = self._parsedotname()
+            imports.append((name, name2))
+            while token != "," and "\n" not in token:
+                tokentype, token, indent = self.next()
+            if token != ",": break
+        return imports
+
+    def _parenparse(self):
+        name = ''
+        names = []
+        level = 1
+        while True:
+            tokentype, token, indent = self.next()
+            if token in (')', ',') and level == 1:
+                if '=' not in name: name = name.replace(' ', '')
+                names.append(name.strip())
+                name = ''
+            if token == '(':
+                level += 1
+                name += "("
+            elif token == ')':
+                level -= 1
+                if level == 0: break
+                else: name += ")"
+            elif token == ',' and level == 1:
+                pass
+            else:
+                name += "%s " % str(token)
+        return names
+
+    def _parsefunction(self,indent):
+        self.scope=self.scope.pop(indent)
+        tokentype, fname, ind = self.next()
+        if tokentype != NAME: return None
+
+        tokentype, open, ind = self.next()
+        if open != '(': return None
+        params=self._parenparse()
+
+        tokentype, colon, ind = self.next()
+        if colon != ':': return None
+
+        return Function(fname,params,indent)
+
+    def _parseclass(self,indent):
+        self.scope=self.scope.pop(indent)
+        tokentype, cname, ind = self.next()
+        if tokentype != NAME: return None
+
+        super = []
+        tokentype, next, ind = self.next()
+        if next == '(':
+            super=self._parenparse()
+        elif next != ':': return None
+
+        return Class(cname,super,indent)
+
+    def _parseassignment(self):
+        assign=''
+        tokentype, token, indent = self.next()
+        if tokentype == tokenize.STRING or token == 'str':  
+            return '""'
+        elif token == '(' or token == 'tuple':
+            return '()'
+        elif token == '[' or token == 'list':
+            return '[]'
+        elif token == '{' or token == 'dict':
+            return '{}'
+        elif tokentype == tokenize.NUMBER:
+            return '0'
+        elif token == 'open' or token == 'file':
+            return 'file'
+        elif token == 'None':
+            return '_PyCmplNoType()'
+        elif token == 'type':
+            return 'type(_PyCmplNoType)' #only for method resolution
+        else:
+            assign += token
+            level = 0
+            while True:
+                tokentype, token, indent = self.next()
+                if token in ('(','{','['):
+                    level += 1
+                elif token in (']','}',')'):
+                    level -= 1
+                    if level == 0: break
+                elif level == 0:
+                    if token in (';','\n'): break
+                    assign += token
+        return "%s" % assign
+
+    def next(self):
+        type, token, (lineno, indent), end, self.parserline = self.gen.next()
+        if lineno == self.curline:
+            #print 'line found [%s] scope=%s' % (line.replace('\n',''),self.scope.name)
+            self.currentscope = self.scope
+        return (type, token, indent)
+
+    def _adjustvisibility(self):
+        newscope = Scope('result',0)
+        scp = self.currentscope
+        while scp != None:
+            if type(scp) == Function:
+                slice = 0
+                #Handle 'self' params
+                if scp.parent != None and type(scp.parent) == Class:
+                    slice = 1
+                    newscope.local('%s = %s' % (scp.params[0],scp.parent.name))
+                for p in scp.params[slice:]:
+                    i = p.find('=')
+                    if len(p) == 0: continue
+                    pvar = ''
+                    ptype = ''
+                    if i == -1:
+                        pvar = p
+                        ptype = '_PyCmplNoType()'
+                    else:
+                        pvar = p[:i]
+                        ptype = _sanitize(p[i+1:])
+                    if pvar.startswith('**'):
+                        pvar = pvar[2:]
+                        ptype = '{}'
+                    elif pvar.startswith('*'):
+                        pvar = pvar[1:]
+                        ptype = '[]'
+
+                    newscope.local('%s = %s' % (pvar,ptype))
+
+            for s in scp.subscopes:
+                ns = s.copy_decl(0)
+                newscope.add(ns)
+            for l in scp.locals: newscope.local(l)
+            scp = scp.parent
+
+        self.currentscope = newscope
+        return self.currentscope
+
+    #p.parse(vim.current.buffer[:],vim.eval("line('.')"))
+    def parse(self,text,curline=0):
+        self.curline = int(curline)
+        buf = cStringIO.StringIO(''.join(text) + '\n')
+        self.gen = tokenize.generate_tokens(buf.readline)
+        self.currentscope = self.scope
+
+        try:
+            freshscope=True
+            while True:
+                tokentype, token, indent = self.next()
+                #dbg( 'main: token=[%s] indent=[%s]' % (token,indent))
+
+                if tokentype == DEDENT or token == "pass":
+                    self.scope = self.scope.pop(indent)
+                elif token == 'def':
+                    func = self._parsefunction(indent)
+                    if func is None:
+                        print "function: syntax error..."
+                        continue
+                    dbg("new scope: function")
+                    freshscope = True
+                    self.scope = self.scope.add(func)
+                elif token == 'class':
+                    cls = self._parseclass(indent)
+                    if cls is None:
+                        print "class: syntax error..."
+                        continue
+                    freshscope = True
+                    dbg("new scope: class")
+                    self.scope = self.scope.add(cls)
+                    
+                elif token == 'import':
+                    imports = self._parseimportlist()
+                    for mod, alias in imports:
+                        loc = "import %s" % mod
+                        if len(alias) > 0: loc += " as %s" % alias
+                        self.scope.local(loc)
+                    freshscope = False
+                elif token == 'from':
+                    mod, token = self._parsedotname()
+                    if not mod or token != "import":
+                        print "from: syntax error..."
+                        continue
+                    names = self._parseimportlist()
+                    for name, alias in names:
+                        loc = "from %s import %s" % (mod,name)
+                        if len(alias) > 0: loc += " as %s" % alias
+                        self.scope.local(loc)
+                    freshscope = False
+                elif tokentype == STRING:
+                    if freshscope: self.scope.doc(token)
+                elif tokentype == NAME:
+                    name,token = self._parsedotname(token) 
+                    if token == '=':
+                        stmt = self._parseassignment()
+                        dbg("parseassignment: %s = %s" % (name, stmt))
+                        if stmt != None:
+                            self.scope.local("%s = %s" % (name,stmt))
+                    freshscope = False
+        except StopIteration: #thrown on EOF
+            pass
+        except:
+            dbg("parse error: %s, %s @ %s" %
+                (sys.exc_info()[0], sys.exc_info()[1], self.parserline))
+        return self._adjustvisibility()
+
+def _sanitize(str):
+    val = ''
+    level = 0
+    for c in str:
+        if c in ('(','{','['):
+            level += 1
+        elif c in (']','}',')'):
+            level -= 1
+        elif level == 0:
+            val += c
+    return val

vimftpplugin/support.py

+# Supporting functions for the Python file type plug-in for Vim.
+# Authors:
+#  - Peter Odding <peter@peterodding.com>
+#  - Bart Kroon <bart@tarmack.eu>
+# Last Change: October 9, 2011
+# URL: https://github.com/tarmack/vim-python-ftplugin
+
+import __builtin__
+import os
+import platform
+import re
+import sys
+
+def complete_modules(base):
+  '''
+  Find the names of the built-in, binary and source modules available on the
+  user's system without executing any Python code except for this function (in
+  other words, module name completion is completely safe).
+  '''
+  # Start with the names of the built-in modules.
+  if base:
+    modulenames = set()
+  else:
+    modulenames = set(sys.builtin_module_names)
+  # Find the installed modules.
+  for root in sys.path:
+    scan_modules(root, [x for x in base.split('.') if x], modulenames)
+  # Sort the module list ignoring case and underscores.
+  if base:
+    base += '.'
+  print '\n'.join(list(modulenames))
+  #return [base + modname for modname in friendly_sort(list(modulenames))]
+
+def scan_modules(directory, base, modulenames):
+  sharedext = platform.system() == 'Windows' and '\.dll' or '\.so'
+  if not os.path.isdir(directory):
+    return
+  for name in os.listdir(directory):
+    pathname = '%s/%s' % (directory, name)
+    if os.path.isdir(pathname):
+      listing = os.listdir(pathname)
+      if '__init__' in [os.path.splitext(x)[0] for x in listing]:
+        if base:
+          if name == base[0]:
+            scan_modules(pathname, base[1:], modulenames)
+        else:
+          modulenames.add(name)
+    elif (not base) and re.match(r'^[A-Za-z0-9_]+(\.py[co]?|%s)$' % sharedext, name):
+      name = os.path.splitext(name)[0]
+      if name != '__init__':
+        modulenames.add(name)
+
+def complete_variables(expr):
+  '''
+  Use __import__() and dir() to get the functions and/or variables available in
+  the given module or submodule.
+  '''
+  todo = [x for x in expr.split('.') if x]
+  done = []
+  attributes = []
+  module = load_module(todo, done)
+  subject = module
+  while todo:
+    if len(todo) == 1:
+      expr = ('.'.join(done) + '.') if done else ''
+      attributes = [expr + attr for attr in dir(subject) if attr.startswith(todo[0])]
+      print '\n'.join(attributes)
+    try:
+      subject = getattr(subject, todo[0])
+      done.append(todo.pop(0))
+    except AttributeError:
+      break
+  if subject:
+    expr = ('.'.join(done) + '.') if done else ''
+    subject = [expr + entry for entry in dir(subject)]
+    print '\n'.join(subject)
+
+def load_module(todo, done):
+  '''
+  Find the most specific valid Python module given a tokenized identifier
+  expression (e.g. `os.path' for `os.path.islink').
+  '''
+  path = []
+  module = __builtin__
+  while todo:
+    path.append(todo[0])
+    try:
+      temp = __import__('.'.join(path))
+      if temp.__name__ == '.'.join(path):
+        module = temp
+      else:
+        break
+    except ImportError:
+      break
+    done.append(todo.pop(0))
+  return module
+
+def find_module_path(name):
+  '''
+  Look for a Python module on the module search path (used for "gf" and
+  searching in imported modules).
+  '''
+  fname = name.replace('.', '/')
+  for directory in sys.path:
+    scriptfile = directory + '/' + fname + '.py'
+    if os.path.isfile(scriptfile):
+      print scriptfile
+      break
+
+# vim: ts=2 sw=2 sts=2 et

vimftpplugin/test_inference.py

+from inference import TypeInferenceEngine
+
+def resolve_simplified(tie, node):
+  nodes = []
+  for parent, kind, context in tie.resolve(node):
+    nodes.append(tie.format(parent))
+  return nodes
+
+source = open('example.py').read()
+tie = TypeInferenceEngine(source)
+shadowed_var = tie.find_node(9, 5)
+global_var = tie.find_node(10, 12)
+kw_indirect = tie.find_node(10, 33)
+kw = tie.find_node(10, 46)
+init_arg = tie.find_node(11, 9)
+
+def test_resolving():
+  assert global_var.id == 'global_var'
+  assert kw_indirect.id == 'kw_indirect'
+  assert init_arg.id == 'init_arg'
+  assert resolve_simplified(tie, global_var) == ['global_var=42']
+  assert resolve_simplified(tie, init_arg) == ['function __init__']
+  assert resolve_simplified(tie, kw_indirect) == ['function __init__']
+  assert sorted(resolve_simplified(tie, shadowed_var)) == sorted(["shadowed=''", "shadowed=[]"])
+
+def test_evaluation():
+  assert list(tie.evaluate(global_var)) == [int]
+  assert list(tie.evaluate(kw)) == [list]
+  assert list(tie.evaluate(kw_indirect)) == [str]
+  assert list(tie.evaluate(tie.find_node(37, 1))) == [list]
+  assert list(tie.evaluate(tie.find_node(37, 1))) == [list]
+  assert list(tie.evaluate(tie.find_node(42, 1))) == [set]
+  assert list(tie.evaluate(tie.find_node(42, 4))) == [dict]
+
+def test_completion():
+  assert 'conjugate' in tie.complete(1, 1)
+  assert 'keys' in tie.complete(42, 4)
+  assert 'append' in tie.complete(48, 7) and 'capitalize' in tie.complete(47, 7)
+  assert 'difference' in tie.complete(51, 13)
+  assert 'test' in tie.complete(22, 13)