--- C:\idlelib_svn\CallTips.py Sat Dec 15 00:59:26 2007 +++ C:\Python25\Lib\idlelib\CallTips.py Sat Dec 15 00:58:55 2007 @@ -5,10 +5,10 @@ which disappear when you type a closing parenthesis. """ -import re import sys import types -import textwrap +import inspect +from repr import repr as limited_len_repr import CallTipWindow from HyperParser import HyperParser @@ -105,7 +105,7 @@ (name,), {}) else: entity = self.get_entity(name) - return get_arg_text(entity) + return get_arg_text_and_doc(entity) def get_entity(self, name): "Lookup name in a namespace spanning sys.modules and __main.dict__" @@ -121,51 +121,60 @@ # Given a class object, return a function object used for the # constructor (ie, __init__() ) or None if we can't find one. try: - return class_ob.__init__.im_func + return class_ob.__init__ except AttributeError: for base in class_ob.__bases__: rc = _find_constructor(base) if rc is not None: return rc return None -def get_arg_text(ob): - """Get a string describing the arguments for the given object""" - arg_text = "" - doc = "" - if ob is not None: - arg_offset = 0 - if type(ob) in (types.ClassType, types.TypeType): - # Look for the highest __init__ in the class chain. - fob = _find_constructor(ob) - if fob is None: - fob = lambda: None - else: - arg_offset = 1 - elif type(ob)==types.MethodType: - # bit of a hack for methods - turn it into a function - # but we drop the "self" param. - fob = ob.im_func +def get_arg_text_and_doc(ob): + """Get the call signature and the doc-string for the given object""" + if ob is None: + return "", "" + + fob = ob + arg_offset = 0 # set to 1 to drop initial argument, e.g. "self" + doc = inspect.getdoc(ob) + if isinstance(ob, (types.ClassType, types.TypeType)): + # Look for the highest __init__ in the class chain + fob = _find_constructor(ob) + if fob is None: + fob = lambda: None + else: arg_offset = 1 + # use __init__'s doc-string if available, otherwise use the + # class's doc-string + doc = inspect.getdoc(fob) or doc + elif isinstance(ob, (types.FunctionType, + types.LambdaType, + types.BuiltinFunctionType)): + pass + elif isinstance(ob, (types.MethodType, types.BuiltinMethodType)): + arg_offset = 1 + elif callable(ob): + try: + fob = ob.__call__ + except AttributeError: + pass else: - fob = ob - # Try to build one for Python defined functions - if type(fob) in [types.FunctionType, types.LambdaType]: - argcount = fob.func_code.co_argcount - real_args = fob.func_code.co_varnames[arg_offset:argcount] - defaults = fob.func_defaults or [] - defaults = list(map(lambda name: "=%s" % repr(name), defaults)) - defaults = [""] * (len(real_args) - len(defaults)) + defaults - items = map(lambda arg, dflt: arg + dflt, real_args, defaults) - if fob.func_code.co_flags & 0x4: - items.append("...") - if fob.func_code.co_flags & 0x8: - items.append("***") - arg_text = ", ".join(items) - arg_text = "(%s)" % re.sub("\.\d+", "", arg_text) - # See if we can use the docstring - doc = getattr(ob, "__doc__", "") - if doc: - doc = textwrap.dedent(doc).strip() + arg_offset = 1 + doc = inspect.getdoc(fob) + if not doc: + doc = "" + + arg_text = "" + # Try to get the call signature for Python defined functions/methods + if isinstance(fob, (types.FunctionType, + types.LambdaType, + types.MethodType)): + args, varargs, varkw, defaults = inspect.getargspec(fob) + def formatvalue(obj): + return "=" + limited_len_repr(obj) + arg_text = inspect.formatargspec(args[arg_offset:], + varargs, varkw, defaults, + formatvalue=formatvalue) + return arg_text, doc ################################################# @@ -176,43 +185,80 @@ def t1(): "()" def t2(a, b=None): "(a, b=None)" - def t3(a, *args): "(a, ...)" - def t4(*args): "(...)" - def t5(a, *args): "(a, ...)" - def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)" - def t7((a, b), c, (d, e)): "(, c, )" - - class TC(object): - "(ai=None, ...)" - def __init__(self, ai=None, *b): "(ai=None, ...)" + def t3(a, *args): "(a, *args)" + def t4(*args): "(*args)" + def t5(a, *args): "(a, *args)" + def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + def t7((a, b), c, (d, e)): "((a, b), c, (d, e))" + + class TC1(object): + "this should not be used at all" + def __init__(self, a=None, *args, **kw): "(a=None, *args, **kw)" + def t1(self): "()" + def t2(self, a, b=None): "(a, b=None)" + def t3(self, a, *args): "(a, *args)" + def t4(self, *args): "(*args)" + def t5(self, a, *args): "(a, *args)" + def t6(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + def t7(self, (a, b), c, (d, e)): "((a, b), c, (d, e))" + @classmethod + def t8(klass, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + @staticmethod + def t9(a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + def __call__(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + + class TC2(): + "this should not be used at all" + def __init__(self, a=None, *args, **kw): "(a=None, *args, **kw)" def t1(self): "()" - def t2(self, ai, b=None): "(ai, b=None)" - def t3(self, ai, *args): "(ai, ...)" - def t4(self, *args): "(...)" - def t5(self, ai, *args): "(ai, ...)" - def t6(self, ai, b=None, *args, **kw): "(ai, b=None, ..., ***)" - def t7(self, (ai, b), c, (d, e)): "(, c, )" + def t2(self, a, b=None): "(a, b=None)" + def t3(self, a, *args): "(a, *args)" + def t4(self, *args): "(*args)" + def t5(self, a, *args): "(a, *args)" + def t6(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + def t7(self, (a, b), c, (d, e)): "((a, b), c, (d, e))" + @classmethod + def t8(klass, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + @staticmethod + def t9(a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + def __call__(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)" + + tc1 = TC1() + tc2 = TC2() + + tests = """t1 t2 t3 t4 t5 t6 t7 + tc1.t1 tc1.t2 tc1.t3 tc1.t4 tc1.t5 tc1.t6 tc1.t7 + TC1 TC1.t8 TC1.t9 tc1 + tc2.t1 tc2.t2 tc2.t3 tc2.t4 tc2.t5 tc2.t6 tc2.t7 + TC2 TC2.t8 TC2.t9 tc2""".split() + builtin_tests = ["int", "dir", "execfile", "[].append", "sys.exit"] - def test(tests): + def test(tests, builtin_tests): ct = CallTips() failed=[] - for t in tests: - expected = t.__doc__ + "\n" + t.__doc__ - name = t.__name__ - # exercise fetch_tip(), not just get_arg_text() - try: - qualified_name = "%s.%s" % (t.im_class.__name__, name) - except AttributeError: - qualified_name = name - arg_text = ct.fetch_tip(qualified_name)[0] - if arg_text != expected: - failed.append(t) - fmt = "%s - expected %s, but got %s" - print fmt % (t.__name__, expected, get_arg_text(t)) - print "%d of %d tests failed" % (len(failed), len(tests)) - - tc = TC() - tests = (t1, t2, t3, t4, t5, t6, t7, - TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6, tc.t7) + for name in tests: + t = eval(name) + if isinstance(t, (types.ClassType, types.TypeType)): + expected = t.__init__.__doc__ + elif isinstance(t, (types.InstanceType, TC1, TC2)): + expected = t.__call__.__doc__ + else: + expected = t.__doc__ + expected = (expected, expected) + # exercise fetch_tip(), not just get_arg_text_and_doc() + arg_text, doc = ct.fetch_tip(name) + if (arg_text, doc) != expected: + failed.append(name) + print "%s - expected %r, but got %r" % (name, expected, + (arg_text, doc)) + for name in builtin_tests: + arg_text, doc = ct.fetch_tip(name) + if arg_text != '' or not doc: + failed.append(name) + expected = "('', )" + print "%s - expected %s, but got %r" % (name, expected, + (arg_text, doc)) + print "%d of %d tests failed" % (len(failed), + len(tests) + len(builtin_tests)) - test(tests) + test(tests, builtin_tests)