forked from VimPlug/jedi
Moved jedi files to separate package folder
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from api import *
|
||||
import settings
|
||||
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
This is a compatibility module, to make it possible to use jedi also with older
|
||||
python versions.
|
||||
"""
|
||||
import sys
|
||||
|
||||
is_py3k = sys.hexversion >= 0x03000000
|
||||
|
||||
is_py25 = sys.hexversion < 0x02060000
|
||||
|
||||
# next was defined in python 2.6, in python 3 obj.next won't be possible
|
||||
# anymore
|
||||
try:
|
||||
next = next
|
||||
except NameError:
|
||||
_raiseStopIteration = object()
|
||||
|
||||
def next(iterator, default=_raiseStopIteration):
|
||||
if not hasattr(iterator, 'next'):
|
||||
raise TypeError("not an iterator")
|
||||
try:
|
||||
return iterator.next()
|
||||
except StopIteration:
|
||||
if default is _raiseStopIteration:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
|
||||
# ast module was defined in python 2.6
|
||||
try:
|
||||
from ast import literal_eval
|
||||
except ImportError:
|
||||
literal_eval = eval
|
||||
|
||||
|
||||
# properties in 2.5
|
||||
try:
|
||||
property.setter
|
||||
except AttributeError:
|
||||
class property(property):
|
||||
def __init__(self, fget, *args, **kwargs):
|
||||
self.__doc__ = fget.__doc__
|
||||
super(property, self).__init__(fget, *args, **kwargs)
|
||||
|
||||
def setter(self, fset):
|
||||
cls_ns = sys._getframe(1).f_locals
|
||||
for k, v in cls_ns.iteritems():
|
||||
if v == self:
|
||||
propname = k
|
||||
break
|
||||
cls_ns[propname] = property(self.fget, fset,
|
||||
self.fdel, self.__doc__)
|
||||
return cls_ns[propname]
|
||||
else:
|
||||
property = property
|
||||
|
||||
# unicode function
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
def unicode(s):
|
||||
return s.decode("utf-8")
|
||||
|
||||
# exec function
|
||||
if is_py3k:
|
||||
def exec_function(source, global_map):
|
||||
exec(source, global_map)
|
||||
else:
|
||||
eval(compile("""def exec_function(source, global_map):
|
||||
exec source in global_map """, 'blub', 'exec'))
|
||||
|
||||
# tokenize function
|
||||
import tokenize
|
||||
if is_py3k:
|
||||
tokenize_func = tokenize.tokenize
|
||||
else:
|
||||
tokenize_func = tokenize.generate_tokens
|
||||
|
||||
# BytesIO (Python 2.5 has no io module)
|
||||
try:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
except ImportError:
|
||||
from io import BytesIO
|
||||
|
||||
# hasattr function used because python
|
||||
if is_py3k:
|
||||
hasattr = hasattr
|
||||
else:
|
||||
def hasattr(obj, name):
|
||||
try:
|
||||
getattr(obj, name)
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
class Python3Method(object):
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, objtype):
|
||||
if obj is None:
|
||||
return lambda *args, **kwargs: self.func(*args, **kwargs)
|
||||
else:
|
||||
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
|
||||
|
||||
try:
|
||||
# the python3 way
|
||||
from functools import reduce
|
||||
except ImportError:
|
||||
reduce = reduce
|
||||
|
||||
|
||||
def use_metaclass(meta, *bases):
|
||||
""" Create a class with a metaclass. """
|
||||
if not bases:
|
||||
bases = (object,)
|
||||
return meta("HackClass", bases, {})
|
||||
|
||||
try:
|
||||
from inspect import cleandoc
|
||||
except ImportError:
|
||||
# python 2.5 doesn't have this method
|
||||
import string
|
||||
|
||||
def cleandoc(doc):
|
||||
"""Clean up indentation from docstrings.
|
||||
|
||||
Any whitespace that can be uniformly removed from the second line
|
||||
onwards is removed."""
|
||||
try:
|
||||
lines = string.split(string.expandtabs(doc), '\n')
|
||||
except UnicodeError:
|
||||
return None
|
||||
else:
|
||||
# Find minimum indentation of any non-blank lines after first line.
|
||||
margin = sys.maxint
|
||||
for line in lines[1:]:
|
||||
content = len(string.lstrip(line))
|
||||
if content:
|
||||
indent = len(line) - content
|
||||
margin = min(margin, indent)
|
||||
# Remove indentation.
|
||||
if lines:
|
||||
lines[0] = lines[0].lstrip()
|
||||
if margin < sys.maxint:
|
||||
for i in range(1, len(lines)):
|
||||
lines[i] = lines[i][margin:]
|
||||
# Remove any trailing or leading blank lines.
|
||||
while lines and not lines[-1]:
|
||||
lines.pop()
|
||||
while lines and not lines[0]:
|
||||
lines.pop(0)
|
||||
return string.join(lines, '\n')
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
import re
|
||||
import weakref
|
||||
|
||||
import parsing
|
||||
import dynamic
|
||||
import imports
|
||||
import evaluate
|
||||
import modules
|
||||
import debug
|
||||
import settings
|
||||
import keywords
|
||||
import helpers
|
||||
|
||||
from _compatibility import next
|
||||
|
||||
__all__ = ['Script', 'NotFoundError', 'set_debug_function']
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
""" A custom error to avoid catching the wrong exceptions """
|
||||
pass
|
||||
|
||||
|
||||
class Completion(object):
|
||||
def __init__(self, name, needs_dot, like_name_length, base):
|
||||
self.name = name
|
||||
self.needs_dot = needs_dot
|
||||
self.like_name_length = like_name_length
|
||||
self._completion_parent = name.parent() # limit gc
|
||||
self.base = base
|
||||
|
||||
@property
|
||||
def complete(self):
|
||||
dot = '.' if self.needs_dot else ''
|
||||
append = ''
|
||||
funcs = (parsing.Function, evaluate.Function)
|
||||
if settings.add_bracket_after_function \
|
||||
and self._completion_parent.isinstance(funcs):
|
||||
append = '('
|
||||
|
||||
if settings.add_dot_after_module:
|
||||
if isinstance(self.base, parsing.Module): append += '.'
|
||||
if isinstance(self.base, parsing.Param):
|
||||
append += '='
|
||||
return dot + self.name.names[-1][self.like_name_length:] + append
|
||||
|
||||
@property
|
||||
def word(self):
|
||||
return str(self.name.names[-1])
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return str(self.name.parent())
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
try:
|
||||
return str(self.name.parent().docstr)
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_type(self):
|
||||
return type(self.name.parent())
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class Definition(dynamic.BaseOutput):
|
||||
def __init__(self, definition):
|
||||
""" The definition of a function """
|
||||
super(Definition, self).__init__(definition.start_pos, definition)
|
||||
self._def_parent = definition.parent() # just here to limit gc
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
d = self.definition
|
||||
if isinstance(d, evaluate.InstanceElement):
|
||||
d = d.var
|
||||
if isinstance(d, evaluate.parsing.Name):
|
||||
d = d.parent()
|
||||
|
||||
if isinstance(d, evaluate.Array):
|
||||
d = 'class ' + d.type
|
||||
elif isinstance(d, (parsing.Class, evaluate.Class, evaluate.Instance)):
|
||||
d = 'class ' + str(d.name)
|
||||
elif isinstance(d, (evaluate.Function, evaluate.parsing.Function)):
|
||||
d = 'def ' + str(d.name)
|
||||
elif isinstance(d, evaluate.parsing.Module):
|
||||
# only show module name
|
||||
d = 'module %s' % self.module_name
|
||||
elif isinstance(d, keywords.Keyword):
|
||||
d = 'keyword %s' % d.name
|
||||
else:
|
||||
d = d.get_code().replace('\n', '')
|
||||
return d
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
try:
|
||||
return str(self.definition.docstr)
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def desc_with_module(self):
|
||||
if self.module_path.endswith('.py') \
|
||||
and not isinstance(self.definition, parsing.Module):
|
||||
position = '@%s' % (self.line_nr)
|
||||
else:
|
||||
# is a builtin or module
|
||||
position = ''
|
||||
return "%s:%s%s" % (self.module_name, self.description, position)
|
||||
|
||||
|
||||
class CallDef(object):
|
||||
def __init__(self, executable, index, call):
|
||||
self.executable = executable
|
||||
self.index = index
|
||||
self.call = call
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
if self.executable.isinstance(evaluate.Function):
|
||||
if isinstance(self.executable, evaluate.InstanceElement):
|
||||
return self.executable.params[1:]
|
||||
return self.executable.params
|
||||
else:
|
||||
try:
|
||||
sub = self.executable.get_subscope_by_name('__init__')
|
||||
return sub.params[1:] # ignore self
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
c = self.call
|
||||
while c.next is not None:
|
||||
c = c.next
|
||||
return c.name.end_pos
|
||||
|
||||
@property
|
||||
def call_name(self):
|
||||
return str(self.executable.name)
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
return self.executable.get_parent_until()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s index %s>' % (self.__class__.__name__, self.executable,
|
||||
self.index)
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""
|
||||
A Script is the base for a completion, goto or whatever call.
|
||||
|
||||
:param source: The source code of the current file
|
||||
:type source: string
|
||||
:param line: The line to complete in.
|
||||
:type line: int
|
||||
:param col: The column to complete in.
|
||||
:type col: int
|
||||
:param source_path: The path in the os, the current module is in.
|
||||
:type source_path: int
|
||||
"""
|
||||
def __init__(self, source, line, column, source_path):
|
||||
self.pos = line, column
|
||||
self.module = modules.ModuleWithCursor(source_path, source=source,
|
||||
position=self.pos)
|
||||
self.parser = self.module.parser
|
||||
self.source_path = source_path
|
||||
|
||||
def complete(self):
|
||||
"""
|
||||
An auto completer for python files.
|
||||
|
||||
:return: list of Completion objects.
|
||||
:rtype: list
|
||||
"""
|
||||
path = self.module.get_path_until_cursor()
|
||||
path, dot, like = self._get_completion_parts(path)
|
||||
|
||||
try:
|
||||
scopes = self._prepare_goto(path, True)
|
||||
except NotFoundError:
|
||||
scope_generator = evaluate.get_names_for_scope(
|
||||
self.parser.user_scope, self.pos)
|
||||
completions = []
|
||||
for scope, name_list in scope_generator:
|
||||
for c in name_list:
|
||||
completions.append((c, scope))
|
||||
else:
|
||||
completions = []
|
||||
debug.dbg('possible scopes', scopes)
|
||||
for s in scopes:
|
||||
# TODO is this really the right way? just ignore the funcs? \
|
||||
# do the magic functions first? and then recheck here?
|
||||
if not isinstance(s, evaluate.Function):
|
||||
if isinstance(s, imports.ImportPath):
|
||||
names = s.get_defined_names(on_import_stmt=True)
|
||||
else:
|
||||
names = s.get_defined_names()
|
||||
for c in names:
|
||||
completions.append((c, s))
|
||||
|
||||
completions = [(c, s) for c, s in completions
|
||||
if settings.case_insensitive_completion
|
||||
and c.names[-1].lower().startswith(like.lower())
|
||||
or c.names[-1].startswith(like)]
|
||||
|
||||
if not dot: # named_params have no dots
|
||||
call_def = self.get_in_function_call()
|
||||
if call_def:
|
||||
if not call_def.module.is_builtin():
|
||||
for p in call_def.params:
|
||||
completions.append((p.get_name(), p))
|
||||
completions += []
|
||||
|
||||
needs_dot = not dot and path
|
||||
completions = set(completions)
|
||||
|
||||
c = [Completion(c, needs_dot, len(like), s) for c, s in completions]
|
||||
|
||||
return c
|
||||
|
||||
def _prepare_goto(self, goto_path, is_like_search=False):
|
||||
scope = self.parser.user_scope
|
||||
debug.dbg('start: %s in %s' % (goto_path, scope))
|
||||
|
||||
user_stmt = self.parser.user_stmt
|
||||
if not user_stmt and len(goto_path.split('\n')) > 1:
|
||||
# If the user_stmt is not defined and the goto_path is multi line,
|
||||
# something's strange. Most probably the backwards tokenizer
|
||||
# matched to much.
|
||||
return []
|
||||
|
||||
if isinstance(user_stmt, parsing.Import):
|
||||
import_names = user_stmt.get_all_import_names()
|
||||
count = 0
|
||||
kill_count = -1
|
||||
for i in import_names:
|
||||
for name_part in i.names:
|
||||
count += 1
|
||||
if self.pos <= name_part.end_pos:
|
||||
kill_count += 1
|
||||
scopes = [imports.ImportPath(user_stmt, is_like_search,
|
||||
kill_count=kill_count, direct_resolve=True)]
|
||||
else:
|
||||
# just parse one statement, take it and evaluate it
|
||||
r = parsing.PyFuzzyParser(goto_path, self.source_path,
|
||||
no_docstr=True)
|
||||
try:
|
||||
stmt = r.module.statements[0]
|
||||
except IndexError:
|
||||
raise NotFoundError()
|
||||
|
||||
stmt.start_pos = self.pos
|
||||
stmt.parent = weakref.ref(scope)
|
||||
scopes = evaluate.follow_statement(stmt)
|
||||
return scopes
|
||||
|
||||
def get_definition(self):
|
||||
"""
|
||||
Returns the definitions of a the path under the cursor. This is
|
||||
not a goto function! This follows complicated paths and returns the
|
||||
end, not the first definition.
|
||||
|
||||
:return: list of Definition objects, which are basically scopes.
|
||||
:rtype: list
|
||||
"""
|
||||
def resolve_import_paths(scopes):
|
||||
for s in scopes.copy():
|
||||
if isinstance(s, imports.ImportPath):
|
||||
scopes.remove(s)
|
||||
scopes.update(resolve_import_paths(set(s.follow())))
|
||||
return scopes
|
||||
|
||||
goto_path = self.module.get_path_under_cursor()
|
||||
|
||||
context = self.module.get_context()
|
||||
if next(context) in ('class', 'def'):
|
||||
scopes = set([self.module.parser.user_scope])
|
||||
elif not goto_path:
|
||||
op = self.module.get_operator_under_cursor()
|
||||
scopes = set([keywords.get_operator(op, self.pos)] if op else [])
|
||||
else:
|
||||
scopes = set(self._prepare_goto(goto_path))
|
||||
|
||||
scopes = resolve_import_paths(scopes)
|
||||
|
||||
# add keywords
|
||||
scopes |= keywords.get_keywords(string=goto_path, pos=self.pos)
|
||||
|
||||
d = set([Definition(s) for s in scopes])
|
||||
return sorted(d, key=lambda x: (x.module_path, x.start_pos))
|
||||
|
||||
def goto(self):
|
||||
goto_path = self.module.get_path_under_cursor()
|
||||
goto_path, dot, search_name = self._get_completion_parts(goto_path)
|
||||
|
||||
# define goto path the right way
|
||||
if not dot:
|
||||
goto_path = search_name
|
||||
search_name_new = None
|
||||
else:
|
||||
search_name_new = search_name
|
||||
|
||||
context = self.module.get_context()
|
||||
if next(context) in ('class', 'def'):
|
||||
definitions = set([self.module.parser.user_scope])
|
||||
else:
|
||||
scopes = self._prepare_goto(goto_path)
|
||||
definitions = evaluate.goto(scopes, search_name_new)
|
||||
|
||||
d = [Definition(d) for d in set(definitions)]
|
||||
return sorted(d, key=lambda x: (x.module_path, x.start_pos))
|
||||
|
||||
def related_names(self):
|
||||
"""
|
||||
Returns `dynamic.RelatedName` objects, which contain all names, that
|
||||
are defined by the same variable, function, class or import.
|
||||
This function can be used either to show all the usages of a variable
|
||||
or for renaming purposes.
|
||||
"""
|
||||
goto_path = self.module.get_path_under_cursor()
|
||||
goto_path, dot, search_name = self._get_completion_parts(goto_path)
|
||||
|
||||
# define goto path the right way
|
||||
if not dot:
|
||||
goto_path = search_name
|
||||
search_name_new = None
|
||||
else:
|
||||
search_name_new = search_name
|
||||
|
||||
context = self.module.get_context()
|
||||
if next(context) in ('class', 'def'):
|
||||
if isinstance(self.module.parser.user_scope, parsing.Function):
|
||||
e = evaluate.Function(self.module.parser.user_scope)
|
||||
else:
|
||||
e = evaluate.Class(self.module.parser.user_scope)
|
||||
definitions = [e]
|
||||
elif isinstance(self.module.parser.user_stmt,
|
||||
(parsing.Param, parsing.Import)):
|
||||
definitions = [self.module.parser.user_stmt]
|
||||
else:
|
||||
scopes = self._prepare_goto(goto_path)
|
||||
definitions = evaluate.goto(scopes, search_name_new)
|
||||
|
||||
module = set([d.get_parent_until() for d in definitions])
|
||||
module.add(self.module.parser.module)
|
||||
names = dynamic.related_names(definitions, search_name, module)
|
||||
|
||||
for d in definitions:
|
||||
if isinstance(d, parsing.Statement):
|
||||
def add_array(arr):
|
||||
calls = dynamic._scan_array(arr, search_name)
|
||||
for call in calls:
|
||||
for n in call.name.names:
|
||||
if n == search_name:
|
||||
names.append(dynamic.RelatedName(n, d))
|
||||
for op, arr in d.assignment_details:
|
||||
add_array(arr)
|
||||
if not d.assignment_details:
|
||||
add_array(d.get_assignment_calls())
|
||||
elif isinstance(d, parsing.Import):
|
||||
is_user = d == self.module.parser.user_stmt
|
||||
check_names = [d.namespace, d.alias, d.from_ns] if is_user \
|
||||
else d.get_defined_names()
|
||||
for name in check_names:
|
||||
if name:
|
||||
for n in name.names:
|
||||
if n.start_pos <= self.pos <= n.end_pos \
|
||||
or not is_user:
|
||||
names.append(dynamic.RelatedName(n, d))
|
||||
elif isinstance(d, parsing.Name):
|
||||
names.append(dynamic.RelatedName(d.names[0], d))
|
||||
else:
|
||||
names.append(dynamic.RelatedName(d.name.names[0], d))
|
||||
|
||||
return sorted(names, key=lambda x: (x.module_path, x.start_pos))
|
||||
|
||||
def get_in_function_call(self):
|
||||
"""
|
||||
Return the function, that the cursor is in, e.g.:
|
||||
>>> isinstance(| # | <-- cursor is here
|
||||
|
||||
This would return the `isinstance` function. In contrary:
|
||||
>>> isinstance()| # | <-- cursor is here
|
||||
|
||||
This would return `None`.
|
||||
"""
|
||||
def scan_array_for_pos(arr, pos):
|
||||
"""
|
||||
Returns the function Call that match search_name in an Array.
|
||||
"""
|
||||
index = 0
|
||||
call = None
|
||||
stop = False
|
||||
for index, sub in enumerate(arr.values):
|
||||
call = None
|
||||
for s in sub:
|
||||
if isinstance(s, parsing.Array):
|
||||
new = scan_array_for_pos(s, pos)
|
||||
if new[0] is not None:
|
||||
call, index, stop = new
|
||||
if stop:
|
||||
return call, index, stop
|
||||
elif isinstance(s, parsing.Call):
|
||||
start_s = s
|
||||
while s is not None:
|
||||
if s.start_pos >= pos:
|
||||
return call, index, stop
|
||||
elif s.execution is not None:
|
||||
end = s.execution.end_pos
|
||||
if s.execution.start_pos < pos and \
|
||||
(end is None or pos < end):
|
||||
c, index, stop = scan_array_for_pos(
|
||||
s.execution, pos)
|
||||
if stop:
|
||||
return c, index, stop
|
||||
|
||||
# call should return without execution and
|
||||
# next
|
||||
reset = c or s
|
||||
reset.execution = None
|
||||
reset.next = None
|
||||
return c or start_s, index, True
|
||||
#else:
|
||||
#return call, index, stop
|
||||
s = s.next
|
||||
# The third return is just necessary for recursion inside, because
|
||||
# it needs to know when to stop iterating.
|
||||
return call, index, stop
|
||||
|
||||
user_stmt = self.parser.user_stmt
|
||||
if user_stmt is None or not isinstance(user_stmt, parsing.Statement):
|
||||
return None
|
||||
ass = helpers.fast_parent_copy(user_stmt.get_assignment_calls())
|
||||
|
||||
call, index, stop = scan_array_for_pos(ass, self.pos)
|
||||
if call is None:
|
||||
return None
|
||||
|
||||
origins = evaluate.follow_call(call)
|
||||
|
||||
if len(origins) == 0:
|
||||
return None
|
||||
# just take entry zero, because we need just one.
|
||||
executable = origins[0]
|
||||
|
||||
after = self.module.get_line(self.pos[0])[self.pos[1]:]
|
||||
index -= re.search('^[ ,]*', after).group(0).count(',')
|
||||
return CallDef(executable, index, call)
|
||||
|
||||
def _get_completion_parts(self, path):
|
||||
"""
|
||||
Returns the parts for the completion
|
||||
:return: tuple - (path, dot, like)
|
||||
"""
|
||||
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
|
||||
return match.groups()
|
||||
|
||||
def __del__(self):
|
||||
evaluate.clear_caches()
|
||||
|
||||
|
||||
def set_debug_function(func_cb):
|
||||
"""
|
||||
You can define a callback debug function to get all the debug messages.
|
||||
:param func_cb: The callback function for debug messages, with n params.
|
||||
"""
|
||||
debug.debug_function = func_cb
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
from _compatibility import exec_function, is_py3k
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
if is_py3k:
|
||||
import io
|
||||
else:
|
||||
import types
|
||||
import inspect
|
||||
|
||||
import debug
|
||||
import parsing
|
||||
|
||||
|
||||
module_find_path = sys.path[1:]
|
||||
|
||||
|
||||
class CachedModule(object):
|
||||
"""
|
||||
The base type for all modules, which is not to be confused with
|
||||
`parsing.Module`. Caching happens here.
|
||||
"""
|
||||
cache = {}
|
||||
|
||||
def __init__(self, path=None, name=None):
|
||||
self.path = path
|
||||
self.name = name
|
||||
self._parser = None
|
||||
|
||||
@property
|
||||
def parser(self):
|
||||
""" get the parser lazy """
|
||||
if not self._parser:
|
||||
try:
|
||||
timestamp, parser = self.cache[self.path or self.name]
|
||||
if not self.path or timestamp == os.path.getmtime(self.path):
|
||||
self._parser = parser
|
||||
else:
|
||||
raise KeyError()
|
||||
except KeyError:
|
||||
self._load_module()
|
||||
return self._parser
|
||||
|
||||
def _get_source(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _load_module(self):
|
||||
source = self._get_source()
|
||||
self._parser = parsing.PyFuzzyParser(source, self.path or self.name)
|
||||
p_time = None if not self.path else os.path.getmtime(self.path)
|
||||
|
||||
self.cache[self.path or self.name] = p_time, self._parser
|
||||
|
||||
|
||||
class Parser(CachedModule):
|
||||
"""
|
||||
This module is a parser for all builtin modules, which are programmed in
|
||||
C/C++. It should also work on third party modules.
|
||||
It can be instantiated with either a path or a name of the module. The path
|
||||
is important for third party modules.
|
||||
|
||||
:param name: The name of the module.
|
||||
:param path: The path of the module.
|
||||
:param sys_path: The sys.path, which is can be customizable.
|
||||
"""
|
||||
|
||||
map_types = {
|
||||
'floating point number': '0.0',
|
||||
'string': '""',
|
||||
'str': '""',
|
||||
'character': '"a"',
|
||||
'integer': '0',
|
||||
'int': '0',
|
||||
'dictionary': '{}',
|
||||
'list': '[]',
|
||||
'file object': 'file("")',
|
||||
# TODO things like dbg: ('not working', 'tuple of integers')
|
||||
}
|
||||
|
||||
if is_py3k:
|
||||
map_types['file object'] = 'import io; return io.TextIOWrapper(file)'
|
||||
|
||||
module_cache = {}
|
||||
|
||||
def __init__(self, path=None, name=None, sys_path=module_find_path):
|
||||
if not name:
|
||||
name = os.path.basename(path)
|
||||
name = name.rpartition('.')[0] # cut file type (normally .so)
|
||||
super(Parser, self).__init__(path=path, name=name)
|
||||
|
||||
self.sys_path = sys_path
|
||||
self._module = None
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
def load_module(name, path):
|
||||
if path:
|
||||
self.sys_path.insert(0, path)
|
||||
|
||||
temp, sys.path = sys.path, self.sys_path
|
||||
content = {}
|
||||
exec_function('import %s as module' % name, content)
|
||||
self._module = content['module']
|
||||
sys.path = temp
|
||||
|
||||
if path:
|
||||
self.sys_path.pop(0)
|
||||
|
||||
# module might already be defined
|
||||
if not self._module:
|
||||
path = self.path
|
||||
name = self.name
|
||||
if self.path:
|
||||
|
||||
dot_path = []
|
||||
p = self.path
|
||||
# search for the builtin with the correct path
|
||||
while p and p not in sys.path:
|
||||
p, sep, mod = p.rpartition(os.path.sep)
|
||||
dot_path.append(mod.partition('.')[0])
|
||||
if p:
|
||||
name = ".".join(reversed(dot_path))
|
||||
path = p
|
||||
else:
|
||||
path = os.path.dirname(self.path)
|
||||
|
||||
load_module(name, path)
|
||||
return self._module
|
||||
|
||||
def _get_source(self):
|
||||
""" Override this abstract method """
|
||||
return self._generate_code(self.module, self._load_mixins())
|
||||
|
||||
def _load_mixins(self):
|
||||
"""
|
||||
Load functions that are mixed in to the standard library.
|
||||
E.g. builtins are written in C (binaries), but my autocompletion only
|
||||
understands Python code. By mixing in Python code, the autocompletion
|
||||
should work much better for builtins.
|
||||
"""
|
||||
regex = r'^(def|class)\s+([\w\d]+)'
|
||||
|
||||
def process_code(code, depth=0):
|
||||
funcs = {}
|
||||
matches = list(re.finditer(regex, code, re.MULTILINE))
|
||||
positions = [m.start() for m in matches]
|
||||
for i, pos in enumerate(positions):
|
||||
try:
|
||||
code_block = code[pos:positions[i + 1]]
|
||||
except IndexError:
|
||||
code_block = code[pos:len(code)]
|
||||
structure_name = matches[i].group(1)
|
||||
name = matches[i].group(2)
|
||||
if structure_name == 'def':
|
||||
funcs[name] = code_block
|
||||
elif structure_name == 'class':
|
||||
if depth > 0:
|
||||
raise NotImplementedError()
|
||||
|
||||
# remove class line
|
||||
c = re.sub(r'^[^\n]+', '', code_block)
|
||||
# remove whitespace
|
||||
c = re.compile(r'^[ ]{4}', re.MULTILINE).sub('', c)
|
||||
|
||||
funcs[name] = process_code(c)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
return funcs
|
||||
|
||||
try:
|
||||
name = self.name
|
||||
if name == '__builtin__' and not is_py3k:
|
||||
name = 'builtins'
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
f = open(os.path.sep.join([path, 'mixin', name]) + '.py')
|
||||
except IOError:
|
||||
return {}
|
||||
else:
|
||||
mixin_dct = process_code(f.read())
|
||||
if is_py3k and self.name == _Builtin.name:
|
||||
# in the case of Py3k xrange is now range
|
||||
mixin_dct['range'] = mixin_dct['xrange']
|
||||
return mixin_dct
|
||||
|
||||
def _generate_code(self, scope, mixin_funcs, depth=0):
|
||||
"""
|
||||
Generate a string, which uses python syntax as an input to the
|
||||
PyFuzzyParser.
|
||||
"""
|
||||
def get_doc(obj, indent=False):
|
||||
doc = inspect.getdoc(obj)
|
||||
if doc:
|
||||
doc = ('r"""\n%s\n"""\n' % doc)
|
||||
if indent:
|
||||
doc = parsing.indent_block(doc)
|
||||
return doc
|
||||
return ''
|
||||
|
||||
def is_in_base_classes(cls, name, comparison):
|
||||
""" Base classes may contain the exact same object """
|
||||
if name in mixin_funcs:
|
||||
return False
|
||||
try:
|
||||
mro = cls.mro()
|
||||
except TypeError:
|
||||
# this happens, if cls == type
|
||||
return False
|
||||
for base in mro[1:]:
|
||||
try:
|
||||
attr = getattr(base, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
if attr == comparison:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_scope_objects(names):
|
||||
"""
|
||||
Looks for the names defined with dir() in an objects and divides
|
||||
them into different object types.
|
||||
"""
|
||||
classes = {}
|
||||
funcs = {}
|
||||
stmts = {}
|
||||
members = {}
|
||||
for n in names:
|
||||
if '__' in n and n not in mixin_funcs:
|
||||
continue
|
||||
try:
|
||||
# this has a builtin_function_or_method
|
||||
exe = getattr(scope, n)
|
||||
except AttributeError:
|
||||
# happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
members[n] = None
|
||||
else:
|
||||
if inspect.isclass(scope):
|
||||
if is_in_base_classes(scope, n, exe):
|
||||
continue
|
||||
if inspect.isbuiltin(exe) or inspect.ismethod(exe) \
|
||||
or inspect.ismethoddescriptor(exe):
|
||||
funcs[n] = exe
|
||||
elif inspect.isclass(exe):
|
||||
classes[n] = exe
|
||||
elif inspect.ismemberdescriptor(exe):
|
||||
members[n] = exe
|
||||
else:
|
||||
stmts[n] = exe
|
||||
return classes, funcs, stmts, members
|
||||
|
||||
code = ''
|
||||
if inspect.ismodule(scope): # generate comment where the code's from.
|
||||
try:
|
||||
path = scope.__file__
|
||||
except AttributeError:
|
||||
path = '?'
|
||||
code += '# Generated module %s from %s\n' % (scope.__name__, path)
|
||||
|
||||
code += get_doc(scope)
|
||||
|
||||
names = set(dir(scope)) - set(['__file__', '__name__', '__doc__',
|
||||
'__path__', '__package__'])
|
||||
|
||||
classes, funcs, stmts, members = get_scope_objects(names)
|
||||
|
||||
# classes
|
||||
for name, cl in classes.items():
|
||||
bases = (c.__name__ for c in cl.__bases__)
|
||||
code += 'class %s(%s):\n' % (name, ','.join(bases))
|
||||
if depth == 0:
|
||||
try:
|
||||
mixin = mixin_funcs[name]
|
||||
except KeyError:
|
||||
mixin = {}
|
||||
cl_code = self._generate_code(cl, mixin, depth + 1)
|
||||
code += parsing.indent_block(cl_code)
|
||||
code += '\n'
|
||||
|
||||
# functions
|
||||
for name, func in funcs.items():
|
||||
params, ret = parse_function_doc(func)
|
||||
if depth > 0:
|
||||
params = 'self, ' + params
|
||||
doc_str = get_doc(func, indent=True)
|
||||
try:
|
||||
mixin = mixin_funcs[name]
|
||||
except KeyError:
|
||||
# normal code generation
|
||||
code += 'def %s(%s):\n' % (name, params)
|
||||
code += doc_str
|
||||
code += parsing.indent_block('%s\n\n' % ret)
|
||||
else:
|
||||
# generation of code with mixins
|
||||
# the parser only supports basic functions with a newline after
|
||||
# the double dots
|
||||
# find doc_str place
|
||||
pos = re.search(r'\):\s*\n', mixin).end()
|
||||
if pos is None:
|
||||
raise Exception("Builtin function not parsed correctly")
|
||||
code += mixin[:pos] + doc_str + mixin[pos:]
|
||||
|
||||
# class members (functions) properties?
|
||||
for name, func in members.items():
|
||||
# recursion problem in properties TODO remove
|
||||
if name in ['fget', 'fset', 'fdel']:
|
||||
continue
|
||||
ret = 'pass'
|
||||
code += '@property\ndef %s(self):\n' % (name)
|
||||
code += parsing.indent_block(get_doc(func) + '%s\n\n' % ret)
|
||||
|
||||
# variables
|
||||
for name, value in stmts.items():
|
||||
if is_py3k:
|
||||
file_type = io.TextIOWrapper
|
||||
else:
|
||||
file_type = types.FileType
|
||||
if type(value) == file_type:
|
||||
value = 'open()'
|
||||
elif name == 'None':
|
||||
value = ''
|
||||
elif type(value).__name__ in ['int', 'bool', 'float',
|
||||
'dict', 'list', 'tuple']:
|
||||
value = repr(value)
|
||||
else:
|
||||
# get the type, if the type is not simple.
|
||||
mod = type(value).__module__
|
||||
value = type(value).__name__ + '()'
|
||||
if mod != '__builtin__':
|
||||
value = '%s.%s' % (mod, value)
|
||||
code += '%s = %s\n' % (name, value)
|
||||
|
||||
if depth == 0:
|
||||
#with open('writeout.py', 'w') as f:
|
||||
# f.write(code)
|
||||
#import sys
|
||||
#sys.stdout.write(code)
|
||||
#exit()
|
||||
pass
|
||||
return code
|
||||
|
||||
|
||||
def parse_function_doc(func):
|
||||
"""
|
||||
Takes a function and returns the params and return value as a tuple.
|
||||
This is nothing more than a docstring parser.
|
||||
"""
|
||||
# TODO: things like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
||||
doc = inspect.getdoc(func)
|
||||
|
||||
# get full string, parse round parentheses: def func(a, (b,c))
|
||||
try:
|
||||
count = 0
|
||||
debug.dbg(func, func.__name__, doc)
|
||||
start = doc.index('(')
|
||||
for i, s in enumerate(doc[start:]):
|
||||
if s == '(':
|
||||
count += 1
|
||||
elif s == ')':
|
||||
count -= 1
|
||||
if count == 0:
|
||||
end = start + i
|
||||
break
|
||||
param_str = doc[start + 1:end]
|
||||
|
||||
# remove square brackets, that show an optional param ( = None)
|
||||
def change_options(m):
|
||||
args = m.group(1).split(',')
|
||||
for i, a in enumerate(args):
|
||||
if a and '=' not in a:
|
||||
args[i] += '=None'
|
||||
return ','.join(args)
|
||||
while True:
|
||||
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
|
||||
change_options, param_str)
|
||||
if changes == 0:
|
||||
break
|
||||
except (ValueError, AttributeError):
|
||||
debug.dbg('no brackets found - no param')
|
||||
end = 0
|
||||
param_str = ''
|
||||
|
||||
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
||||
|
||||
if doc is not None:
|
||||
r = re.search('-[>-]* ', doc[end:end + 7])
|
||||
if doc is None or r is None:
|
||||
ret = 'pass'
|
||||
else:
|
||||
index = end + r.end()
|
||||
# get result type, which can contain newlines
|
||||
pattern = re.compile(r'(,\n|[^\n-])+')
|
||||
ret_str = pattern.match(doc, index).group(0).strip()
|
||||
# New object -> object()
|
||||
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
|
||||
|
||||
ret = Parser.map_types.get(ret_str, ret_str)
|
||||
if ret == ret_str and ret not in ['None', 'object', 'tuple', 'set']:
|
||||
debug.dbg('not working', ret_str)
|
||||
if ret != 'pass':
|
||||
ret = ('return ' if 'return' not in ret else '') + ret
|
||||
return param_str, ret
|
||||
|
||||
|
||||
class _Builtin(object):
|
||||
""" The builtin scope / module """
|
||||
# Python 3 compatibility
|
||||
if is_py3k:
|
||||
name = 'builtins'
|
||||
else:
|
||||
name = '__builtin__'
|
||||
_builtins = Parser(name=name)
|
||||
|
||||
builtin_scope = _Builtin()._builtins.parser.module
|
||||
@@ -0,0 +1,45 @@
|
||||
import inspect
|
||||
try:
|
||||
# Use colorama for nicer console output.
|
||||
from colorama import Fore, init
|
||||
init()
|
||||
except ImportError:
|
||||
class Fore(object):
|
||||
RED = ''
|
||||
GREEN = ''
|
||||
RESET = ''
|
||||
|
||||
NOTICE = object()
|
||||
WARNING = object()
|
||||
ERROR = object()
|
||||
|
||||
debug_function = None
|
||||
ignored_modules = ['parsing', 'builtin']
|
||||
|
||||
|
||||
def dbg(*args):
|
||||
""" Looks at the stack, to see if a debug message should be printed. """
|
||||
if debug_function:
|
||||
frm = inspect.stack()[1]
|
||||
mod = inspect.getmodule(frm[0])
|
||||
if not (mod.__name__ in ignored_modules):
|
||||
debug_function(NOTICE, *args)
|
||||
|
||||
|
||||
def warning(*args):
|
||||
if debug_function:
|
||||
debug_function(WARNING, *args)
|
||||
|
||||
|
||||
def error(*args):
|
||||
if debug_function:
|
||||
debug_function(ERROR, *args)
|
||||
|
||||
|
||||
def print_to_stdout(level, *args):
|
||||
""" The default debug function """
|
||||
msg = (Fore.GREEN + 'dbg: ' if level == NOTICE else Fore.RED + 'warning: ')
|
||||
print(msg + ', '.join(str(a) for a in args) + Fore.RESET)
|
||||
|
||||
|
||||
#debug_function = print_to_stdout
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
"""
|
||||
For dynamic completion.
|
||||
|
||||
Sorry to everyone who is reading this code. Especially the array parts are
|
||||
really cryptic and not understandable. It's just a hack, that turned out to be
|
||||
working quite good.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
import parsing
|
||||
import modules
|
||||
import evaluate
|
||||
import helpers
|
||||
import settings
|
||||
import debug
|
||||
|
||||
# This is something like the sys.path, but only for searching params. It means
|
||||
# that this is the order in which Jedi searches params.
|
||||
search_param_modules = ['.']
|
||||
search_param_cache = {}
|
||||
|
||||
|
||||
def search_param_memoize(func):
|
||||
"""
|
||||
Is only good for search params memoize, respectively the closure,
|
||||
because it just caches the input, not the func, like normal memoize does.
|
||||
"""
|
||||
def wrapper(*args, **kwargs):
|
||||
key = (args, frozenset(kwargs.items()))
|
||||
if key in search_param_cache:
|
||||
return search_param_cache[key]
|
||||
else:
|
||||
rv = func(*args, **kwargs)
|
||||
search_param_cache[key] = rv
|
||||
return rv
|
||||
return wrapper
|
||||
|
||||
|
||||
class ParamListener(object):
|
||||
"""
|
||||
This listener is used to get the params for a function.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.param_possibilities = []
|
||||
|
||||
def execute(self, params):
|
||||
self.param_possibilities.append(params)
|
||||
|
||||
|
||||
@evaluate.memoize_default([])
|
||||
def search_params(param):
|
||||
"""
|
||||
This is a dynamic search for params. If you try to complete a type:
|
||||
>>> def func(foo):
|
||||
>>> # here is the completion
|
||||
>>> foo
|
||||
>>> func(1)
|
||||
>>> func("")
|
||||
|
||||
It is not known what the type is, because it cannot be guessed with
|
||||
recursive madness. Therefore one has to analyse the statements that are
|
||||
calling the function, as well as analyzing the incoming params.
|
||||
"""
|
||||
if not settings.dynamic_params:
|
||||
return []
|
||||
|
||||
def get_params_for_module(module):
|
||||
"""
|
||||
Returns the values of a param, or an empty array.
|
||||
"""
|
||||
@search_param_memoize
|
||||
def get_posibilities(module, func_name):
|
||||
try:
|
||||
possible_stmts = module.used_names[func_name]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
for stmt in possible_stmts:
|
||||
evaluate.follow_statement(stmt)
|
||||
return listener.param_possibilities
|
||||
|
||||
result = []
|
||||
for params in get_posibilities(module, func_name):
|
||||
for p in params:
|
||||
if str(p) == param_name:
|
||||
result += evaluate.follow_statement(p.parent())
|
||||
#print listener.param_possibilities, param, result
|
||||
|
||||
return result
|
||||
|
||||
func = param.get_parent_until(parsing.Function)
|
||||
current_module = param.get_parent_until()
|
||||
func_name = str(func.name)
|
||||
if func_name == '__init__' and isinstance(func.parent(), parsing.Class):
|
||||
func_name = str(func.parent().name)
|
||||
|
||||
# get the param name
|
||||
if param.assignment_details:
|
||||
arr = param.assignment_details[0][1]
|
||||
else:
|
||||
arr = param.get_assignment_calls()
|
||||
offset = 1 if arr[0][0] in ['*', '**'] else 0
|
||||
param_name = str(arr[0][offset].name)
|
||||
|
||||
# add the listener
|
||||
listener = ParamListener()
|
||||
func.listeners.add(listener)
|
||||
|
||||
result = get_params_for_module(current_module)
|
||||
|
||||
# TODO check other modules
|
||||
# cleanup: remove the listener; important: should not stick.
|
||||
func.listeners.remove(listener)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_array_additions(array):
|
||||
""" Just a mapper function for the internal _check_array_additions """
|
||||
if array._array.type not in ['list', 'set']:
|
||||
# TODO also check for dict updates
|
||||
return []
|
||||
|
||||
is_list = array._array.type == 'list'
|
||||
current_module = array._array.parent_stmt().get_parent_until()
|
||||
res = _check_array_additions(array, current_module, is_list)
|
||||
return res
|
||||
|
||||
counter = 0
|
||||
def dec(func):
|
||||
""" TODO delete this """
|
||||
def wrapper(*args, **kwargs):
|
||||
global counter
|
||||
element = args[0]
|
||||
if isinstance(element, evaluate.Array):
|
||||
stmt = element._array.parent_stmt()
|
||||
else:
|
||||
# must be instance
|
||||
stmt = element.var_args.parent_stmt()
|
||||
print(' ' * counter + 'recursion,', stmt)
|
||||
counter += 1
|
||||
res = func(*args, **kwargs)
|
||||
counter -= 1
|
||||
#print ' '*counter + 'end,'
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
|
||||
def _scan_array(arr, search_name):
|
||||
""" Returns the function Call that match search_name in an Array. """
|
||||
result = []
|
||||
for sub in arr:
|
||||
for s in sub:
|
||||
if isinstance(s, parsing.Array):
|
||||
result += _scan_array(s, search_name)
|
||||
elif isinstance(s, parsing.Call):
|
||||
while s is not None:
|
||||
n = s.name
|
||||
if isinstance(n, parsing.Name) and search_name in n.names:
|
||||
result.append(s)
|
||||
|
||||
if s.execution is not None:
|
||||
result += _scan_array(s.execution, search_name)
|
||||
s = s.next
|
||||
return result
|
||||
|
||||
#@dec
|
||||
@evaluate.memoize_default([])
|
||||
def _check_array_additions(compare_array, module, is_list):
|
||||
"""
|
||||
Checks if a `parsing.Array` has "add" statements:
|
||||
>>> a = [""]
|
||||
>>> a.append(1)
|
||||
"""
|
||||
if not settings.dynamic_array_additions or module.is_builtin():
|
||||
return []
|
||||
|
||||
def check_calls(calls, add_name):
|
||||
"""
|
||||
Calls are processed here. The part before the call is searched and
|
||||
compared with the original Array.
|
||||
"""
|
||||
result = []
|
||||
for c in calls:
|
||||
call_path = list(c.generate_call_path())
|
||||
separate_index = call_path.index(add_name)
|
||||
if not len(call_path) > separate_index + 1:
|
||||
# this means that there is no execution -> [].append
|
||||
continue
|
||||
backtrack_path = iter(call_path[:separate_index])
|
||||
|
||||
position = c.parent_stmt().start_pos
|
||||
scope = c.parent_stmt().parent()
|
||||
|
||||
found = evaluate.follow_call_path(backtrack_path, scope, position)
|
||||
if not compare_array in found:
|
||||
continue
|
||||
|
||||
params = call_path[separate_index + 1]
|
||||
if not params.values:
|
||||
continue # no params: just ignore it
|
||||
if add_name in ['append', 'add']:
|
||||
result += evaluate.follow_call_list(params)
|
||||
elif add_name in ['insert']:
|
||||
try:
|
||||
second_param = params[1]
|
||||
except IndexError:
|
||||
continue
|
||||
else:
|
||||
result += evaluate.follow_call_list([second_param])
|
||||
elif add_name in ['extend', 'update']:
|
||||
iterators = evaluate.follow_call_list(params)
|
||||
result += evaluate.get_iterator_types(iterators)
|
||||
return result
|
||||
|
||||
def get_execution_parent(element, *stop_classes):
|
||||
""" Used to get an Instance/Execution parent """
|
||||
if isinstance(element, evaluate.Array):
|
||||
stmt = element._array.parent_stmt()
|
||||
else:
|
||||
# must be instance
|
||||
stmt = element.var_args.parent_stmt()
|
||||
if isinstance(stmt, evaluate.InstanceElement):
|
||||
stop_classes = list(stop_classes) + [evaluate.Function]
|
||||
return stmt.get_parent_until(stop_classes)
|
||||
|
||||
search_names = ['append', 'extend', 'insert'] if is_list else \
|
||||
['add', 'update']
|
||||
comp_arr_parent = get_execution_parent(compare_array, evaluate.Execution)
|
||||
possible_stmts = []
|
||||
res = []
|
||||
for n in search_names:
|
||||
try:
|
||||
possible_stmts += module.used_names[n]
|
||||
except KeyError:
|
||||
continue
|
||||
for stmt in possible_stmts:
|
||||
# Check if the original scope is an execution. If it is, one
|
||||
# can search for the same statement, that is in the module
|
||||
# dict. Executions are somewhat special in jedi, since they
|
||||
# literally copy the contents of a function.
|
||||
if isinstance(comp_arr_parent, evaluate.Execution):
|
||||
stmt = comp_arr_parent. \
|
||||
get_statement_for_position(stmt.start_pos)
|
||||
if stmt is None:
|
||||
continue
|
||||
# InstanceElements are special, because they don't get copied,
|
||||
# but have this wrapper around them.
|
||||
if isinstance(comp_arr_parent, evaluate.InstanceElement):
|
||||
stmt = evaluate.InstanceElement(comp_arr_parent.instance, stmt)
|
||||
|
||||
if evaluate.follow_statement.push_stmt(stmt):
|
||||
# check recursion
|
||||
continue
|
||||
res += check_calls(_scan_array(stmt.get_assignment_calls(), n), n)
|
||||
evaluate.follow_statement.pop_stmt()
|
||||
return res
|
||||
|
||||
|
||||
def check_array_instances(instance):
|
||||
""" Used for set() and list() instances. """
|
||||
if not settings.dynamic_arrays_instances:
|
||||
return instance.var_args
|
||||
ai = ArrayInstance(instance)
|
||||
return helpers.generate_param_array([ai], instance.var_args.parent_stmt())
|
||||
|
||||
|
||||
class ArrayInstance(parsing.Base):
|
||||
"""
|
||||
Used for the usage of set() and list().
|
||||
This is definitely a hack, but a good one :-)
|
||||
It makes it possible to use set/list conversions.
|
||||
"""
|
||||
def __init__(self, instance):
|
||||
self.instance = instance
|
||||
self.var_args = instance.var_args
|
||||
|
||||
def iter_content(self):
|
||||
"""
|
||||
The index is here just ignored, because of all the appends, etc.
|
||||
lists/sets are too complicated too handle that.
|
||||
"""
|
||||
items = []
|
||||
for array in evaluate.follow_call_list(self.var_args):
|
||||
if isinstance(array, evaluate.Instance) and len(array.var_args):
|
||||
temp = array.var_args[0][0]
|
||||
if isinstance(temp, ArrayInstance):
|
||||
# prevent recursions
|
||||
# TODO compare Modules
|
||||
if self.var_args.start_pos != temp.var_args.start_pos:
|
||||
items += temp.iter_content()
|
||||
else:
|
||||
debug.warning('ArrayInstance recursion', self.var_args)
|
||||
continue
|
||||
items += evaluate.get_iterator_types([array])
|
||||
|
||||
module = self.var_args.parent_stmt().get_parent_until()
|
||||
is_list = str(self.instance.name) == 'list'
|
||||
items += _check_array_additions(self.instance, module, is_list)
|
||||
return items
|
||||
|
||||
|
||||
def related_names(definitions, search_name, modules):
|
||||
def check_call(call):
|
||||
result = []
|
||||
follow = [] # There might be multiple search_name's in one call_path
|
||||
call_path = list(call.generate_call_path())
|
||||
for i, name in enumerate(call_path):
|
||||
if name == search_name:
|
||||
follow.append(call_path[:i + 1])
|
||||
|
||||
for f in follow:
|
||||
scope = call.parent_stmt().parent()
|
||||
evaluate.statement_path = []
|
||||
position = call.parent_stmt().start_pos
|
||||
if len(f) > 1:
|
||||
f, search = f[:-1], f[-1]
|
||||
else:
|
||||
search = None
|
||||
scopes = evaluate.follow_call_path(iter(f), scope, position)
|
||||
follow_res = evaluate.goto(scopes, search, statement_path_offset=0)
|
||||
|
||||
# compare to see if they match
|
||||
if True in [r in definitions for r in follow_res]:
|
||||
l = f[-1] # the NamePart object
|
||||
scope = call.parent_stmt()
|
||||
result.append(RelatedName(l, scope))
|
||||
|
||||
return result
|
||||
|
||||
names = []
|
||||
# TODO check modules in the same directoy
|
||||
for m in modules:
|
||||
if not m.path.endswith('.py'):
|
||||
# don't search for names in builtin modules
|
||||
continue
|
||||
try:
|
||||
stmts = m.used_names[search_name]
|
||||
except KeyError:
|
||||
continue
|
||||
#TODO check heritage of statements
|
||||
for stmt in stmts:
|
||||
for call in _scan_array(stmt.get_assignment_calls(), search_name):
|
||||
names += check_call(call)
|
||||
return names
|
||||
|
||||
|
||||
class BaseOutput(object):
|
||||
def __init__(self, start_pos, definition):
|
||||
self.module_path = str(definition.get_parent_until().path)
|
||||
self.start_pos = start_pos
|
||||
self.definition = definition
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
path = self.module_path
|
||||
sep = os.path.sep
|
||||
p = re.sub(r'^.*?([\w\d]+)(%s__init__)?.py$' % sep, r'\1', path)
|
||||
return p
|
||||
|
||||
def in_builtin_module(self):
|
||||
return not self.module_path.endswith('.py')
|
||||
|
||||
@property
|
||||
def line_nr(self):
|
||||
return self.start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
return self.start_pos[1]
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
raise NotImplementedError('Base Class')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (self.__class__.__name__, self.description)
|
||||
|
||||
|
||||
class RelatedName(BaseOutput):
|
||||
def __init__(self, name_part, scope):
|
||||
super(RelatedName, self).__init__(name_part.start_pos, scope)
|
||||
self.text = str(name_part)
|
||||
self.end_pos = name_part.end_pos
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "%s@%s,%s" % (self.text, self.start_pos[0], self.start_pos[1])
|
||||
+1529
File diff suppressed because it is too large
Load Diff
+197
@@ -0,0 +1,197 @@
|
||||
import copy
|
||||
import weakref
|
||||
|
||||
from _compatibility import hasattr
|
||||
import parsing
|
||||
import evaluate
|
||||
import debug
|
||||
import builtin
|
||||
import settings
|
||||
|
||||
|
||||
class RecursionDecorator(object):
|
||||
"""
|
||||
A decorator to detect recursions in statements. In a recursion a statement
|
||||
at the same place, in the same module may not be executed two times.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
self.reset()
|
||||
|
||||
def __call__(self, stmt, *args, **kwargs):
|
||||
#print stmt, len(self.node_statements())
|
||||
if self.push_stmt(stmt):
|
||||
return []
|
||||
else:
|
||||
result = self.func(stmt, *args, **kwargs)
|
||||
self.pop_stmt()
|
||||
return result
|
||||
|
||||
def push_stmt(self, stmt):
|
||||
self.current = RecursionNode(stmt, self.current)
|
||||
if self._check_recursion():
|
||||
debug.warning('catched recursion', stmt)
|
||||
self.pop_stmt()
|
||||
return True
|
||||
return False
|
||||
|
||||
def pop_stmt(self):
|
||||
self.current = self.current.parent
|
||||
|
||||
def _check_recursion(self):
|
||||
test = self.current
|
||||
while True:
|
||||
test = test.parent
|
||||
if self.current == test:
|
||||
return True
|
||||
if not test:
|
||||
return False
|
||||
|
||||
def reset(self):
|
||||
self.top = None
|
||||
self.current = None
|
||||
|
||||
def node_statements(self):
|
||||
result = []
|
||||
n = self.current
|
||||
while n:
|
||||
result.append(n.stmt)
|
||||
n = n.parent
|
||||
return result
|
||||
|
||||
|
||||
class RecursionNode(object):
|
||||
""" A node of the RecursionDecorator. """
|
||||
def __init__(self, stmt, parent):
|
||||
self.script = stmt.get_parent_until()
|
||||
self.position = stmt.start_pos
|
||||
self.parent = parent
|
||||
self.stmt = stmt
|
||||
|
||||
# Don't check param instances, they are not causing recursions
|
||||
# The same's true for the builtins, because the builtins are really
|
||||
# simple.
|
||||
self.is_ignored = isinstance(stmt, parsing.Param) \
|
||||
or (self.script == builtin.builtin_scope)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return None
|
||||
return self.script == other.script \
|
||||
and self.position == other.position \
|
||||
and not self.is_ignored and not other.is_ignored
|
||||
|
||||
|
||||
class ExecutionRecursionDecorator(object):
|
||||
"""
|
||||
Catches recursions of executions.
|
||||
It is designed like a Singelton. Only one instance should exist.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
self.reset()
|
||||
|
||||
def __call__(self, execution, evaluate_generator=False):
|
||||
debug.dbg('Execution recursions: ', execution, self.recursion_level,
|
||||
self.execution_count, len(self.execution_funcs))
|
||||
if self.check_recursion(execution, evaluate_generator):
|
||||
result = []
|
||||
else:
|
||||
result = self.func(execution, evaluate_generator)
|
||||
self.cleanup()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def cleanup(cls):
|
||||
cls.parent_execution_funcs.pop()
|
||||
cls.recursion_level -= 1
|
||||
|
||||
@classmethod
|
||||
def check_recursion(cls, execution, evaluate_generator):
|
||||
in_par_execution_funcs = execution.base in cls.parent_execution_funcs
|
||||
in_execution_funcs = execution.base in cls.execution_funcs
|
||||
cls.recursion_level += 1
|
||||
cls.execution_count += 1
|
||||
cls.execution_funcs.add(execution.base)
|
||||
cls.parent_execution_funcs.append(execution.base)
|
||||
|
||||
if isinstance(execution.base, (evaluate.Generator, evaluate.Array)):
|
||||
return False
|
||||
module = execution.get_parent_until()
|
||||
if evaluate_generator or module == builtin.builtin_scope:
|
||||
return False
|
||||
|
||||
if in_par_execution_funcs:
|
||||
if cls.recursion_level > settings.max_function_recursion_level:
|
||||
return True
|
||||
if in_execution_funcs and \
|
||||
len(cls.execution_funcs) > settings.max_until_execution_unique:
|
||||
return True
|
||||
if cls.execution_count > settings.max_executions:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
cls.recursion_level = 0
|
||||
cls.parent_execution_funcs = []
|
||||
cls.execution_funcs = set()
|
||||
cls.execution_count = 0
|
||||
|
||||
|
||||
def fast_parent_copy(obj):
|
||||
"""
|
||||
Much, much faster than copy.deepcopy, but just for certain elements.
|
||||
"""
|
||||
new_elements = {}
|
||||
|
||||
def recursion(obj):
|
||||
new_obj = copy.copy(obj)
|
||||
new_elements[obj] = new_obj
|
||||
|
||||
for key, value in new_obj.__dict__.items():
|
||||
if key in ['parent', '_parent', '_parent_stmt', 'parent_stmt']:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
new_obj.__dict__[key] = list_rec(value)
|
||||
elif isinstance(value, parsing.Simple):
|
||||
new_obj.__dict__[key] = recursion(value)
|
||||
|
||||
if obj.parent is not None:
|
||||
try:
|
||||
new_obj.parent = weakref.ref(new_elements[obj.parent()])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if hasattr(obj, 'parent_stmt') and obj.parent_stmt is not None:
|
||||
p = obj.parent_stmt()
|
||||
try:
|
||||
new_obj.parent_stmt = weakref.ref(new_elements[p])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return new_obj
|
||||
|
||||
def list_rec(list_obj):
|
||||
copied_list = list_obj[:] # lists, tuples, strings, unicode
|
||||
for i, el in enumerate(copied_list):
|
||||
if isinstance(el, (parsing.Simple, parsing.Call)):
|
||||
copied_list[i] = recursion(el)
|
||||
elif isinstance(el, list):
|
||||
copied_list[i] = list_rec(el)
|
||||
return copied_list
|
||||
return recursion(obj)
|
||||
|
||||
|
||||
def generate_param_array(args_tuple, parent_stmt=None):
|
||||
""" This generates an array, that can be used as a param. """
|
||||
values = []
|
||||
for arg in args_tuple:
|
||||
if arg is None:
|
||||
values.append([])
|
||||
else:
|
||||
values.append([arg])
|
||||
pos = None
|
||||
arr = parsing.Array(pos, parsing.Array.TUPLE, parent_stmt, values=values)
|
||||
evaluate.faked_scopes.append(arr)
|
||||
return arr
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import pkgutil
|
||||
import imp
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import builtin
|
||||
import modules
|
||||
import debug
|
||||
import parsing
|
||||
import evaluate
|
||||
|
||||
|
||||
class ModuleNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImportPath(object):
|
||||
"""
|
||||
An ImportPath is the path of a `parsing.Import` object.
|
||||
"""
|
||||
class _GlobalNamespace(object):
|
||||
def get_defined_names(self):
|
||||
return []
|
||||
|
||||
def get_imports(self):
|
||||
return []
|
||||
|
||||
GlobalNamespace = _GlobalNamespace()
|
||||
|
||||
def __init__(self, import_stmt, is_like_search=False, kill_count=0,
|
||||
direct_resolve=False):
|
||||
self.import_stmt = import_stmt
|
||||
self.is_like_search = is_like_search
|
||||
self.direct_resolve = direct_resolve
|
||||
self.is_partial_import = bool(kill_count)
|
||||
self.file_path = os.path.dirname(import_stmt.get_parent_until().path)
|
||||
|
||||
# rest is import_path resolution
|
||||
self.import_path = []
|
||||
if import_stmt.from_ns:
|
||||
self.import_path += import_stmt.from_ns.names
|
||||
if import_stmt.namespace:
|
||||
if self.is_nested_import() and not direct_resolve:
|
||||
self.import_path.append(import_stmt.namespace.names[0])
|
||||
else:
|
||||
self.import_path += import_stmt.namespace.names
|
||||
|
||||
for i in range(kill_count + int(is_like_search)):
|
||||
self.import_path.pop()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.import_stmt)
|
||||
|
||||
def is_nested_import(self):
|
||||
"""
|
||||
This checks for the special case of nested imports, without aliases and
|
||||
from statement:
|
||||
>>> import foo.bar
|
||||
"""
|
||||
return not self.import_stmt.alias and not self.import_stmt.from_ns \
|
||||
and len(self.import_stmt.namespace.names) > 1 \
|
||||
and not self.direct_resolve
|
||||
|
||||
def get_nested_import(self, parent):
|
||||
"""
|
||||
See documentation of `self.is_nested_import`.
|
||||
Generates an Import statement, that can be used to fake nested imports.
|
||||
"""
|
||||
i = self.import_stmt
|
||||
# This is not an existing Import statement. Therefore, set position to
|
||||
# None.
|
||||
zero = (None, None)
|
||||
n = parsing.Name(i.namespace.names[1:], zero, zero)
|
||||
new = parsing.Import(zero, zero, n)
|
||||
new.parent = weakref.ref(parent)
|
||||
evaluate.faked_scopes.append(new)
|
||||
debug.dbg('Generated a nested import: %s' % new)
|
||||
return new
|
||||
|
||||
def get_defined_names(self, on_import_stmt=False):
|
||||
names = []
|
||||
for scope in self.follow():
|
||||
if scope is ImportPath.GlobalNamespace:
|
||||
if self.import_stmt.relative_count == 0:
|
||||
names += self.get_module_names()
|
||||
|
||||
path = os.path.abspath(self.file_path)
|
||||
for i in range(self.import_stmt.relative_count - 1):
|
||||
path = os.path.dirname(path)
|
||||
names += self.get_module_names([path])
|
||||
else:
|
||||
if on_import_stmt and isinstance(scope, parsing.Module) \
|
||||
and scope.path.endswith('__init__.py'):
|
||||
pkg_path = os.path.dirname(scope.path)
|
||||
names += self.get_module_names([pkg_path])
|
||||
for s, scope_names in evaluate.get_names_for_scope(scope,
|
||||
include_builtin=False):
|
||||
for n in scope_names:
|
||||
if self.import_stmt.from_ns is None \
|
||||
or self.is_partial_import:
|
||||
# from_ns must be defined to access module
|
||||
# values plus a partial import means that there
|
||||
# is something after the import, which
|
||||
# automatically implies that there must not be
|
||||
# any non-module scope.
|
||||
continue
|
||||
names.append(n)
|
||||
return names
|
||||
|
||||
def get_module_names(self, search_path=None):
|
||||
"""
|
||||
Get the names of all modules in the search_path. This means file names
|
||||
and not names defined in the files.
|
||||
"""
|
||||
if not search_path:
|
||||
search_path = self.sys_path_with_modifications()
|
||||
names = []
|
||||
for module_loader, name, is_pkg in pkgutil.iter_modules(search_path):
|
||||
inf_pos = (float('inf'), float('inf'))
|
||||
names.append(parsing.Name([(name, inf_pos)], inf_pos, inf_pos))
|
||||
return names
|
||||
|
||||
def sys_path_with_modifications(self):
|
||||
module = self.import_stmt.get_parent_until()
|
||||
return modules.sys_path_with_modifications(module)
|
||||
|
||||
def follow(self):
|
||||
"""
|
||||
Returns the imported modules.
|
||||
"""
|
||||
if self.import_path:
|
||||
try:
|
||||
scope, rest = self._follow_file_system()
|
||||
except ModuleNotFound:
|
||||
debug.warning('Module not found: ' + str(self.import_stmt))
|
||||
return []
|
||||
return [ImportPath.GlobalNamespace]
|
||||
|
||||
if len(rest) > 1 or rest and self.is_like_search:
|
||||
scopes = []
|
||||
elif rest:
|
||||
scopes = list(evaluate.follow_path(iter(rest), scope))
|
||||
else:
|
||||
scopes = [scope]
|
||||
|
||||
new = []
|
||||
for scope in scopes:
|
||||
new += remove_star_imports(scope)
|
||||
scopes += new
|
||||
|
||||
if self.is_nested_import():
|
||||
scopes.append(self.get_nested_import(scope))
|
||||
else:
|
||||
scopes = [ImportPath.GlobalNamespace]
|
||||
debug.dbg('after import', scopes)
|
||||
return scopes
|
||||
|
||||
def _follow_file_system(self):
|
||||
"""
|
||||
Find a module with a path (of the module, like usb.backend.libusb10).
|
||||
"""
|
||||
def follow_str(ns, string):
|
||||
debug.dbg('follow_module', ns, string)
|
||||
path = None
|
||||
if ns:
|
||||
path = ns[1]
|
||||
elif self.import_stmt.relative_count:
|
||||
module = self.import_stmt.get_parent_until()
|
||||
path = os.path.abspath(module.path)
|
||||
for i in range(self.import_stmt.relative_count):
|
||||
path = os.path.dirname(path)
|
||||
|
||||
if path is not None:
|
||||
return imp.find_module(string, [path])
|
||||
else:
|
||||
debug.dbg('search_module', string, self.file_path)
|
||||
# Override the sys.path. It works only good that way.
|
||||
# Injecting the path directly into `find_module` did not work.
|
||||
sys.path, temp = sys_path_mod, sys.path
|
||||
try:
|
||||
i = imp.find_module(string)
|
||||
except ImportError:
|
||||
sys.path = temp
|
||||
raise
|
||||
sys.path = temp
|
||||
return i
|
||||
|
||||
sys_path_mod = self.sys_path_with_modifications()
|
||||
current_namespace = None
|
||||
sys_path_mod.insert(0, self.file_path)
|
||||
# now execute those paths
|
||||
rest = []
|
||||
for i, s in enumerate(self.import_path):
|
||||
try:
|
||||
current_namespace = follow_str(current_namespace, s)
|
||||
except ImportError:
|
||||
if current_namespace:
|
||||
rest = self.import_path[i:]
|
||||
else:
|
||||
raise ModuleNotFound(
|
||||
'The module you searched has not been found')
|
||||
|
||||
sys_path_mod.pop(0)
|
||||
path = current_namespace[1]
|
||||
is_package_directory = current_namespace[2][2] == imp.PKG_DIRECTORY
|
||||
|
||||
f = None
|
||||
if is_package_directory or current_namespace[0]:
|
||||
# is a directory module
|
||||
if is_package_directory:
|
||||
path += '/__init__.py'
|
||||
with open(path) as f:
|
||||
source = f.read()
|
||||
else:
|
||||
source = current_namespace[0].read()
|
||||
if path.endswith('.py'):
|
||||
f = modules.Module(path, source)
|
||||
else:
|
||||
f = builtin.Parser(path=path)
|
||||
else:
|
||||
f = builtin.Parser(name=path)
|
||||
|
||||
return f.parser.module, rest
|
||||
|
||||
|
||||
def strip_imports(scopes):
|
||||
"""
|
||||
Here we strip the imports - they don't get resolved necessarily.
|
||||
Really used anymore? Merge with remove_star_imports?
|
||||
"""
|
||||
result = []
|
||||
for s in scopes:
|
||||
if isinstance(s, parsing.Import):
|
||||
# this is something like a statement following.
|
||||
evaluate.statement_path.append(s)
|
||||
result += ImportPath(s).follow()
|
||||
else:
|
||||
result.append(s)
|
||||
return result
|
||||
|
||||
|
||||
def remove_star_imports(scope):
|
||||
"""
|
||||
Check a module for star imports:
|
||||
>>> from module import *
|
||||
|
||||
and follow these modules.
|
||||
"""
|
||||
modules = strip_imports(i for i in scope.get_imports() if i.star)
|
||||
new = []
|
||||
for m in modules:
|
||||
new += remove_star_imports(m)
|
||||
modules += new
|
||||
|
||||
# Filter duplicate modules.
|
||||
return set(modules)
|
||||
@@ -0,0 +1,75 @@
|
||||
import keyword
|
||||
|
||||
from _compatibility import is_py3k
|
||||
import builtin
|
||||
|
||||
import pydoc
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
except ImportError:
|
||||
# Python 2.6
|
||||
try:
|
||||
import pydoc_topics
|
||||
except ImportError:
|
||||
# Python 2.5
|
||||
pydoc_topics = None
|
||||
|
||||
if is_py3k:
|
||||
keys = keyword.kwlist
|
||||
else:
|
||||
keys = keyword.kwlist + ['None', 'False', 'True']
|
||||
|
||||
|
||||
def get_keywords(string='', pos=(0, 0), all=False):
|
||||
if all:
|
||||
return set([Keyword(k, pos) for k in keys])
|
||||
if string in keys:
|
||||
return set([Keyword(string, pos)])
|
||||
return set()
|
||||
|
||||
|
||||
def get_operator(string, pos):
|
||||
return Keyword(string, pos)
|
||||
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, name, pos):
|
||||
self.name = name
|
||||
self.parent = lambda: None
|
||||
self.start_pos = pos
|
||||
|
||||
def get_parent_until(self):
|
||||
return builtin.builtin_scope
|
||||
|
||||
@property
|
||||
def docstr(self):
|
||||
return imitate_pydoc(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
def imitate_pydoc(string):
|
||||
"""
|
||||
It's not possible to get the pydoc's without starting the annoying pager
|
||||
stuff.
|
||||
"""
|
||||
h = pydoc.help
|
||||
try:
|
||||
# try to access symbols
|
||||
string = h.symbols[string]
|
||||
string, _, related = string.partition(' ')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
get_target = lambda s: h.topics.get(s, h.keywords.get(s))
|
||||
while type(string) == type(''):
|
||||
string = get_target(string)
|
||||
|
||||
try:
|
||||
# is a tuple now
|
||||
label, related = string
|
||||
except TypeError:
|
||||
return ''
|
||||
|
||||
return pydoc_topics.topics[label] if pydoc_topics else ''
|
||||
@@ -0,0 +1,9 @@
|
||||
class partial():
|
||||
def __init__(self, func, *args, **keywords):
|
||||
self.__func = func
|
||||
self.__args = args
|
||||
self.__keywords = keywords
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# I know this doesn't work in Python, but Jedi can this ;-)
|
||||
return self.__func(*self.__args, *args, **self.keywords, **kwargs)
|
||||
@@ -0,0 +1,99 @@
|
||||
def compile():
|
||||
class SRE_Match():
|
||||
endpos = 1
|
||||
lastgroup = 0
|
||||
lastindex = 1
|
||||
pos = 0
|
||||
string = 'a'
|
||||
regs = ((0, 1),)
|
||||
|
||||
def __init__(self, pattern):
|
||||
self.re = pattern
|
||||
|
||||
def start(self):
|
||||
return 0
|
||||
|
||||
def end(self):
|
||||
return 1
|
||||
|
||||
def span(self):
|
||||
return 0, 1
|
||||
|
||||
def expand(self):
|
||||
return ''
|
||||
|
||||
def group(self):
|
||||
return ''
|
||||
|
||||
def groupdict(self):
|
||||
return {'a', 'a'}
|
||||
|
||||
def groups(self):
|
||||
return ('a',)
|
||||
|
||||
class SRE_Pattern():
|
||||
flags = 0
|
||||
groupindex = {}
|
||||
groups = 0
|
||||
pattern = 'a'
|
||||
|
||||
def findall(self):
|
||||
"""
|
||||
findall(string[, pos[, endpos]]) --> list.
|
||||
Return a list of all non-overlapping matches of pattern in string.
|
||||
"""
|
||||
return ['a']
|
||||
|
||||
def finditer(self):
|
||||
"""
|
||||
finditer(string[, pos[, endpos]]) --> iterator.
|
||||
Return an iterator over all non-overlapping matches for the
|
||||
RE pattern in string. For each match, the iterator returns a
|
||||
match object.
|
||||
"""
|
||||
yield SRE_Match(self)
|
||||
|
||||
def match(self):
|
||||
"""
|
||||
match(string[, pos[, endpos]]) --> match object or None.
|
||||
Matches zero or more characters at the beginning of the string
|
||||
pattern
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def scanner(self):
|
||||
pass
|
||||
|
||||
def search(self):
|
||||
"""
|
||||
search(string[, pos[, endpos]]) --> match object or None.
|
||||
Scan through string looking for a match, and return a corresponding
|
||||
MatchObject instance. Return None if no position in the string matches.
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def split(self):
|
||||
"""
|
||||
split(string[, maxsplit = 0]) --> list.
|
||||
Split string by the occurrences of pattern.
|
||||
"""
|
||||
return ['a']
|
||||
|
||||
def sub(self):
|
||||
"""
|
||||
sub(repl, string[, count = 0]) --> newstring
|
||||
Return the string obtained by replacing the leftmost non-overlapping
|
||||
occurrences of pattern in string by the replacement repl.
|
||||
"""
|
||||
return ''
|
||||
|
||||
def subn(self):
|
||||
"""
|
||||
subn(repl, string[, count = 0]) --> (newstring, number of subs)
|
||||
Return the tuple (new_string, number_of_subs_made) found by replacing
|
||||
the leftmost non-overlapping occurrences of pattern with the
|
||||
replacement repl.
|
||||
"""
|
||||
return ('', 1)
|
||||
|
||||
return SRE_Pattern()
|
||||
@@ -0,0 +1,8 @@
|
||||
def proxy(object, callback=None):
|
||||
return object
|
||||
|
||||
class ref():
|
||||
def __init__(self, object, callback=None):
|
||||
self.__object = object
|
||||
def __call__(self):
|
||||
return self.__object
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
Pure Python implementation of some builtins.
|
||||
This code is not going to be executed anywhere.
|
||||
These implementations are not always correct, but should work as good as
|
||||
possible for the auto completion.
|
||||
"""
|
||||
|
||||
|
||||
def next(iterator, default=None):
|
||||
if hasattr("next"):
|
||||
return iterator.next()
|
||||
else:
|
||||
return iterator.__next__()
|
||||
return default
|
||||
|
||||
|
||||
def iter(collection, sentinel=None):
|
||||
if sentinel:
|
||||
yield collection()
|
||||
else:
|
||||
for c in collection:
|
||||
yield c
|
||||
|
||||
|
||||
def range(start, stop=None, step=1):
|
||||
return [0]
|
||||
|
||||
|
||||
class xrange():
|
||||
# Attention: this function doesn't exist in Py3k (there it is range).
|
||||
def __iter__(self):
|
||||
yield 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# descriptors
|
||||
#--------------------------------------------------------
|
||||
class property():
|
||||
def __init__(self, fget, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
self.fset(obj, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
self.fdel(obj)
|
||||
|
||||
def setter(self, func):
|
||||
self.fset = func
|
||||
return self
|
||||
|
||||
def getter(self, func):
|
||||
self.fget = func
|
||||
return self
|
||||
|
||||
def deleter(self, func):
|
||||
self.fdel = func
|
||||
return self
|
||||
|
||||
|
||||
class staticmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.__func
|
||||
|
||||
|
||||
class classmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
def _method(*args, **kwargs):
|
||||
return self.__func(cls, *args, **kwargs)
|
||||
return _method
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# array stuff
|
||||
#--------------------------------------------------------
|
||||
class list():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def pop(self):
|
||||
return self.__iterable[-1]
|
||||
|
||||
|
||||
class tuple():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
|
||||
class set():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def pop(self):
|
||||
return self.__iterable.pop()
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
|
||||
class frozenset():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
|
||||
class dict():
|
||||
def __init__(self, **elements):
|
||||
self.__elements = elements
|
||||
|
||||
def clear(self):
|
||||
# has a strange docstr
|
||||
pass
|
||||
|
||||
def get(self, k, d=None):
|
||||
# TODO implement
|
||||
try:
|
||||
#return self.__elements[k]
|
||||
pass
|
||||
except KeyError:
|
||||
return d
|
||||
|
||||
|
||||
class reversed():
|
||||
def __init__(self, sequence):
|
||||
self.__sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__sequence:
|
||||
yield i
|
||||
|
||||
def __next__(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# basic types
|
||||
#--------------------------------------------------------
|
||||
class int():
|
||||
def __init__(self, x, base=None):
|
||||
pass
|
||||
|
||||
|
||||
class str():
|
||||
def __init__(self, obj):
|
||||
pass
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
from _compatibility import exec_function
|
||||
|
||||
import re
|
||||
import tokenize
|
||||
import sys
|
||||
import os
|
||||
|
||||
import parsing
|
||||
import builtin
|
||||
import debug
|
||||
import evaluate
|
||||
|
||||
|
||||
class Module(builtin.CachedModule):
|
||||
"""
|
||||
Manages all files, that are parsed and caches them.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param path: The module path of the file.
|
||||
"""
|
||||
def __init__(self, path, source):
|
||||
super(Module, self).__init__(path=path)
|
||||
self.source = source
|
||||
self._line_cache = None
|
||||
|
||||
def _get_source(self):
|
||||
s = self.source
|
||||
del self.source # memory efficiency
|
||||
return s
|
||||
|
||||
|
||||
class ModuleWithCursor(Module):
|
||||
"""
|
||||
Manages all files, that are parsed and caches them.
|
||||
Important are the params source and path, one of them has to
|
||||
be there.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param path: The module path of the file.
|
||||
:param position: The position, the user is currently in. Only important \
|
||||
for the main file.
|
||||
"""
|
||||
def __init__(self, path, source, position):
|
||||
super(ModuleWithCursor, self).__init__(path, source)
|
||||
self.position = position
|
||||
|
||||
# this two are only used, because there is no nonlocal in Python 2
|
||||
self._line_temp = None
|
||||
self._relevant_temp = None
|
||||
|
||||
# Call the parser already here, because it will be used anyways.
|
||||
# Also, the position is here important (which will not be used by
|
||||
# default), therefore fill the cache here.
|
||||
self._parser = parsing.PyFuzzyParser(source, path, position)
|
||||
|
||||
def get_path_until_cursor(self):
|
||||
""" Get the path under the cursor. """
|
||||
result = self._get_path_until_cursor()
|
||||
self._start_cursor_pos = self._line_temp + 1, self._column_temp
|
||||
return result
|
||||
|
||||
def _get_path_until_cursor(self, start_pos=None):
|
||||
def fetch_line():
|
||||
line = self.get_line(self._line_temp)
|
||||
if self._is_first:
|
||||
self._is_first = False
|
||||
self._line_length = self._column_temp
|
||||
line = line[:self._column_temp]
|
||||
else:
|
||||
self._line_length = len(line)
|
||||
line = line + '\n'
|
||||
# add lines with a backslash at the end
|
||||
while 1:
|
||||
self._line_temp -= 1
|
||||
last_line = self.get_line(self._line_temp)
|
||||
if last_line and last_line[-1] == '\\':
|
||||
line = last_line[:-1] + ' ' + line
|
||||
else:
|
||||
break
|
||||
return line[::-1]
|
||||
|
||||
self._is_first = True
|
||||
if start_pos is None:
|
||||
self._line_temp = self.position[0]
|
||||
self._column_temp = self.position[1]
|
||||
else:
|
||||
self._line_temp, self._column_temp = start_pos
|
||||
|
||||
open_brackets = ['(', '[', '{']
|
||||
close_brackets = [')', ']', '}']
|
||||
|
||||
gen = tokenize.generate_tokens(fetch_line)
|
||||
string = ''
|
||||
level = 0
|
||||
force_point = False
|
||||
try:
|
||||
for token_type, tok, start, end, line in gen:
|
||||
#print 'tok', token_type, tok, force_point
|
||||
if level > 0:
|
||||
if tok in close_brackets:
|
||||
level += 1
|
||||
if tok in open_brackets:
|
||||
level -= 1
|
||||
elif tok == '.':
|
||||
force_point = False
|
||||
elif force_point:
|
||||
# it is reversed, therefore a number is getting recognized
|
||||
# as a floating point number
|
||||
if token_type == tokenize.NUMBER and tok[0] == '.':
|
||||
force_point = False
|
||||
else:
|
||||
break
|
||||
elif tok in close_brackets:
|
||||
level += 1
|
||||
elif token_type in [tokenize.NAME, tokenize.STRING]:
|
||||
force_point = True
|
||||
elif token_type == tokenize.NUMBER:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
self._column_temp = self._line_length - end[1]
|
||||
string += tok
|
||||
except tokenize.TokenError:
|
||||
debug.warning("Tokenize couldn't finish", sys.exc_info)
|
||||
|
||||
return string[::-1]
|
||||
|
||||
def get_path_under_cursor(self):
|
||||
"""
|
||||
Return the path under the cursor. If there is a rest of the path left,
|
||||
it will be added to the stuff before it.
|
||||
"""
|
||||
line = self.get_line(self.position[0])
|
||||
after = re.search("[\w\d]*", line[self.position[1]:]).group(0)
|
||||
return self.get_path_until_cursor() + after
|
||||
|
||||
def get_operator_under_cursor(self):
|
||||
line = self.get_line(self.position[0])
|
||||
after = re.match("[^\w\s]+", line[self.position[1]:])
|
||||
before = re.match("[^\w\s]+", line[:self.position[1]][::-1])
|
||||
return (before.group(0) if before is not None else '') \
|
||||
+ (after.group(0) if after is not None else '')
|
||||
|
||||
def get_context(self):
|
||||
pos = self._start_cursor_pos
|
||||
while pos > (1, 0):
|
||||
# remove non important white space
|
||||
line = self.get_line(pos[0])
|
||||
while pos[1] > 0 and line[pos[1] - 1].isspace():
|
||||
pos = pos[0], pos[1] - 1
|
||||
|
||||
try:
|
||||
yield self._get_path_until_cursor(start_pos=pos)
|
||||
except StopIteration:
|
||||
yield ''
|
||||
pos = self._line_temp, self._column_temp
|
||||
|
||||
while True:
|
||||
yield ''
|
||||
|
||||
def get_line(self, line_nr):
|
||||
if not self._line_cache:
|
||||
self._line_cache = self.source.split('\n')
|
||||
|
||||
if line_nr == 0:
|
||||
# This is a fix for the zeroth line. We need a newline there, for
|
||||
# the backwards parser.
|
||||
return ''
|
||||
if line_nr < 0:
|
||||
raise StopIteration()
|
||||
try:
|
||||
return self._line_cache[line_nr - 1]
|
||||
except IndexError:
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
@evaluate.memoize_default([])
|
||||
def sys_path_with_modifications(module):
|
||||
def execute_code(code):
|
||||
c = "import os; from os.path import *; result=%s"
|
||||
variables = {'__file__': module.path}
|
||||
try:
|
||||
exec_function(c % code, variables)
|
||||
except Exception:
|
||||
debug.warning('sys path detected, but failed to evaluate')
|
||||
return None
|
||||
try:
|
||||
return os.path.abspath(variables['result'])
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def check_module(module):
|
||||
try:
|
||||
possible_stmts = module.used_names['path']
|
||||
except KeyError:
|
||||
return builtin.module_find_path
|
||||
|
||||
sys_path = list(builtin.module_find_path) # copy
|
||||
for p in possible_stmts:
|
||||
try:
|
||||
call = p.get_assignment_calls().get_only_subelement()
|
||||
except AttributeError:
|
||||
continue
|
||||
n = call.name
|
||||
if not isinstance(n, parsing.Name) or len(n.names) != 3:
|
||||
continue
|
||||
if n.names[:2] != ('sys', 'path'):
|
||||
continue
|
||||
array_cmd = n.names[2]
|
||||
if call.execution is None:
|
||||
continue
|
||||
exe = call.execution
|
||||
if not (array_cmd == 'insert' and len(exe) == 2 \
|
||||
or array_cmd == 'append' and len(exe) == 1):
|
||||
continue
|
||||
|
||||
if array_cmd == 'insert':
|
||||
exe_type, exe.type = exe.type, parsing.Array.NOARRAY
|
||||
exe_pop = exe.values.pop(0)
|
||||
res = execute_code(exe.get_code())
|
||||
if res is not None:
|
||||
sys_path.insert(0, res)
|
||||
exe.type = exe_type
|
||||
exe.values.insert(0, exe_pop)
|
||||
elif array_cmd == 'append':
|
||||
res = execute_code(exe.get_code())
|
||||
if res is not None:
|
||||
sys_path.append(res)
|
||||
return sys_path
|
||||
|
||||
curdir = os.path.abspath(os.curdir)
|
||||
try:
|
||||
os.chdir(os.path.dirname(module.path))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
result = check_module(module)
|
||||
|
||||
# cleanup, back to old directory
|
||||
os.chdir(curdir)
|
||||
return result
|
||||
+1761
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
# ----------------
|
||||
# completion output settings
|
||||
# ----------------
|
||||
|
||||
# The completion is by default case insensitive.
|
||||
case_insensitive_completion = True
|
||||
|
||||
# Adds a dot after a module, because a module that is not accessed this way is
|
||||
# definitely not the normal case. However, in VIM this doesn't work, that's why
|
||||
# it isn't used at the moment.
|
||||
add_dot_after_module = False
|
||||
|
||||
# Adds an opening bracket after a function, because that's normal behaviour.
|
||||
add_bracket_after_function = True
|
||||
|
||||
|
||||
# ----------------
|
||||
# dynamic stuff
|
||||
# ----------------
|
||||
|
||||
# check for `append`, etc. on array instances like list()
|
||||
dynamic_arrays_instances = True
|
||||
# check for `append`, etc. on arrays: [], {}, ()
|
||||
dynamic_array_additions = True
|
||||
|
||||
# A dynamic param completion, finds the callees of the function, which define
|
||||
# the params of a function.
|
||||
dynamic_params = True
|
||||
|
||||
# ----------------
|
||||
# recursions
|
||||
# ----------------
|
||||
|
||||
# Recursion settings are important if you don't want extremly recursive python
|
||||
# code to go absolutely crazy. First of there is a global limit
|
||||
# `max_executions`. This limit is important, to set a maximum amount of time,
|
||||
# the completion may use.
|
||||
#
|
||||
# The `max_until_execution_unique` limit is probably the most important one,
|
||||
# because if that limit is passed, functions can only be one time executed. So
|
||||
# new functions will be executed, complex recursions with the same functions
|
||||
# again and again, are ignored.
|
||||
#
|
||||
# `max_function_recursion_level` is more about whether the recursions are
|
||||
# stopped in deepth or in width. The ratio beetween this and
|
||||
# `max_until_execution_unique` is important here. It stops a recursion (after
|
||||
# the number of function calls in the recursion), if it was already used
|
||||
# earlier.
|
||||
#
|
||||
# The values are based on my experimental tries, used on the jedi library. But
|
||||
# I don't think there's any other Python library, that uses recursion in a
|
||||
# similar (extreme) way. This makes the completion definitely worse in some
|
||||
# cases. But a completion should also be fast.
|
||||
|
||||
max_function_recursion_level = 5
|
||||
max_until_execution_unique = 50
|
||||
max_executions = 1000
|
||||
Reference in New Issue
Block a user