Skip to content

Commit dc7e1e6

Browse files
committed
Move argspec inspection code to a new module.
1 parent 6f761ad commit dc7e1e6

File tree

2 files changed

+238
-206
lines changed

2 files changed

+238
-206
lines changed

bpython/cli.py

Lines changed: 25 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import string
4444
import socket
4545
import pydoc
46-
import types
4746
import unicodedata
4847
import textwrap
4948
from cStringIO import StringIO
@@ -61,6 +60,7 @@
6160
from bpython.formatter import BPythonFormatter, Parenthesis
6261

6362
# This for completion
63+
from bpython import inspection
6464
from bpython import importcompletion
6565
from glob import glob
6666

@@ -97,71 +97,6 @@ def calculate_screen_lines(tokens, width, cursor=0):
9797
return lines
9898

9999

100-
def parsekeywordpairs(signature):
101-
tokens = PythonLexer().get_tokens(signature)
102-
stack = []
103-
substack = []
104-
parendepth = 0
105-
begin = False
106-
for token, value in tokens:
107-
if not begin:
108-
if token is Token.Punctuation and value == u'(':
109-
begin = True
110-
continue
111-
112-
if token is Token.Punctuation:
113-
if value == u'(':
114-
parendepth += 1
115-
elif value == u')':
116-
parendepth -= 1
117-
elif value == ':' and parendepth == -1:
118-
# End of signature reached
119-
break
120-
121-
if parendepth > 0:
122-
substack.append(value)
123-
continue
124-
125-
if (token is Token.Punctuation and
126-
(value == ',' or (value == ')' and parendepth == -1))):
127-
stack.append(substack[:])
128-
del substack[:]
129-
continue
130-
131-
if value and value.strip():
132-
substack.append(value)
133-
134-
d = {}
135-
for item in stack:
136-
if len(item) >= 3:
137-
d[item[0]] = ''.join(item[2:])
138-
return d
139-
140-
def fixlongargs(f, argspec):
141-
"""Functions taking default arguments that are references to other objects
142-
whose str() is too big will cause breakage, so we swap out the object
143-
itself with the name it was referenced with in the source by parsing the
144-
source itself !"""
145-
if argspec[3] is None:
146-
# No keyword args, no need to do anything
147-
return
148-
values = list(argspec[3])
149-
if not values:
150-
return
151-
keys = argspec[0][-len(values):]
152-
try:
153-
src = inspect.getsourcelines(f)
154-
except IOError:
155-
return
156-
signature = ''.join(src[0])
157-
kwparsed = parsekeywordpairs(signature)
158-
159-
for i, (key, value) in enumerate(zip(keys, values)):
160-
if len(str(value)) != len(kwparsed[key]):
161-
values[i] = kwparsed[key]
162-
163-
argspec[3] = values
164-
165100
class FakeStdin(object):
166101
"""Provide a fake stdin type for things like raw_input() etc."""
167102

@@ -414,54 +349,6 @@ def writetb(self, l):
414349
fancy."""
415350
map(self.write, ["\x01%s\x03%s" % (OPTS.color_scheme['error'], i) for i in l])
416351

417-
class AttrCleaner(object):
418-
"""A context manager that tries to make an object not exhibit side-effects
419-
on attribute lookup."""
420-
def __init__(self, obj):
421-
self.obj = obj
422-
423-
def __enter__(self):
424-
"""Try to make an object not exhibit side-effects on attribute
425-
lookup."""
426-
type_ = type(self.obj)
427-
__getattribute__ = None
428-
__getattr__ = None
429-
# Dark magic:
430-
# If __getattribute__ doesn't exist on the class and __getattr__ does
431-
# then __getattr__ will be called when doing
432-
# getattr(type_, '__getattribute__', None)
433-
# so we need to first remove the __getattr__, then the
434-
# __getattribute__, then look up the attributes and then restore the
435-
# original methods. :-(
436-
# The upshot being that introspecting on an object to display its
437-
# attributes will avoid unwanted side-effects.
438-
if py3 or type_ != types.InstanceType:
439-
__getattr__ = getattr(type_, '__getattr__', None)
440-
if __getattr__ is not None:
441-
try:
442-
setattr(type_, '__getattr__', (lambda _: None))
443-
except TypeError:
444-
__getattr__ = None
445-
__getattribute__ = getattr(type_, '__getattribute__', None)
446-
if __getattribute__ is not None:
447-
try:
448-
setattr(type_, '__getattribute__', object.__getattribute__)
449-
except TypeError:
450-
# XXX: This happens for e.g. built-in types
451-
__getattribute__ = None
452-
self.attribs = (__getattribute__, __getattr__)
453-
# /Dark magic
454-
455-
def __exit__(self, exc_type, exc_val, exc_tb):
456-
"""Restore an object's magic methods."""
457-
type_ = type(self.obj)
458-
__getattribute__, __getattr__ = self.attribs
459-
# Dark magic:
460-
if __getattribute__ is not None:
461-
setattr(type_, '__getattribute__', __getattribute__)
462-
if __getattr__ is not None:
463-
setattr(type_, '__getattr__', __getattr__)
464-
# /Dark magic
465352

466353
class Repl(object):
467354
"""Implements the necessary guff for a Python-repl-alike interface
@@ -569,7 +456,7 @@ def attr_matches(self, text):
569456
# a SyntaxError
570457
return []
571458
obj = eval(expr, self.interp.locals)
572-
with AttrCleaner(obj):
459+
with inspection.AttrCleaner(obj):
573460
matches = self.attr_lookup(obj, expr, attr)
574461
return matches
575462

@@ -591,7 +478,7 @@ def attr_lookup(self, obj, expr, attr):
591478

592479
def _callable_postfix(self, value, word):
593480
"""rlcompleter's _callable_postfix done right."""
594-
with AttrCleaner(value):
481+
with inspection.AttrCleaner(value):
595482
if hasattr(value, '__call__'):
596483
word += '('
597484
return word
@@ -643,98 +530,10 @@ def get_args(self):
643530

644531
self.current_func = None
645532

646-
def getpydocspec(f, func):
647-
try:
648-
argspec = pydoc.getdoc(f)
649-
except NameError:
650-
return None
651-
652-
rx = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)')
653-
s = rx.search(argspec)
654-
if s is None:
655-
return None
656-
657-
if not hasattr(f, '__name__') or s.groups()[0] != f.__name__:
658-
return None
659-
660-
args = list()
661-
defaults = list()
662-
varargs = varkwargs = None
663-
kwonly_args = list()
664-
kwonly_defaults = dict()
665-
for arg in s.group(2).split(','):
666-
arg = arg.strip()
667-
if arg.startswith('**'):
668-
varkwargs = arg[2:]
669-
elif arg.startswith('*'):
670-
varargs = arg[1:]
671-
else:
672-
arg, _, default = arg.partition('=')
673-
if varargs is not None:
674-
kwonly_args.append(arg)
675-
if default:
676-
kwonly_defaults[arg] = default
677-
else:
678-
args.append(arg)
679-
if default:
680-
defaults.append(default)
681-
682-
return [func, (args, varargs, varkwargs, defaults,
683-
kwonly_args, kwonly_defaults)]
684-
685-
def getargspec(func):
686-
try:
687-
if func in self.interp.locals:
688-
f = self.interp.locals[func]
689-
except TypeError:
690-
return None
691-
else:
692-
try:
693-
f = eval(func, self.interp.locals)
694-
except Exception:
695-
# Same deal with the exceptions :(
696-
return None
697-
else:
698-
self.current_func = f
699-
700-
is_bound_method = inspect.ismethod(f) and f.im_self is not None
701-
try:
702-
if inspect.isclass(f):
703-
self.current_func = f
704-
if py3:
705-
argspec = inspect.getfullargspec(f.__init__)
706-
else:
707-
argspec = inspect.getargspec(f.__init__)
708-
self.current_func = f.__init__
709-
is_bound_method = True
710-
else:
711-
if py3:
712-
argspec = inspect.getfullargspec(f)
713-
else:
714-
argspec = inspect.getargspec(f)
715-
self.current_func = f
716-
argspec = list(argspec)
717-
fixlongargs(f, argspec)
718-
self.argspec = [func, argspec, is_bound_method]
719-
return True
720-
721-
except (NameError, TypeError, KeyError):
722-
with AttrCleaner(f):
723-
t = getpydocspec(f, func)
724-
if t is None:
725-
return None
726-
self.argspec = t
727-
if inspect.ismethoddescriptor(f):
728-
self.argspec[1][0].insert(0, 'obj')
729-
self.argspec.append(is_bound_method)
730-
return True
731-
except AttributeError:
732-
# This happens if no __init__ is found
733-
return None
734-
735533
if not OPTS.arg_spec:
736534
return False
737535

536+
# Find the name of the current function
738537
stack = [['', 0, '']]
739538
try:
740539
for (token, value) in PythonLexer().get_tokens(self.s):
@@ -762,7 +561,27 @@ def getargspec(func):
762561
except IndexError:
763562
return False
764563

765-
if getargspec(func):
564+
# We found a name, now get a function object
565+
try:
566+
if func in self.interp.locals:
567+
f = self.interp.locals[func]
568+
except TypeError:
569+
return None
570+
else:
571+
try:
572+
f = eval(func, self.interp.locals)
573+
except Exception:
574+
# Same deal with the exceptions :(
575+
return None
576+
if inspect.isclass(f):
577+
try:
578+
f = f.__init__
579+
except AttributeError:
580+
return None
581+
self.current_func = f
582+
583+
self.argspec = inspection.getargspec(func, f)
584+
if self.argspec:
766585
self.argspec.append(arg_number)
767586
return True
768587
return False

0 commit comments

Comments
 (0)