diff -r 84c8127453cf Doc/library/rlcompleter.rst --- a/Doc/library/rlcompleter.rst Sat Oct 06 03:40:10 2012 +0200 +++ b/Doc/library/rlcompleter.rst Sun Oct 14 22:55:53 2012 +0200 @@ -49,20 +49,27 @@ Completer Objects ----------------- +.. class:: Completer(namespace=None) + + *namespace* should be the :attr:`__dict__` + in the desidered namespace (like `builtins.__dict__`). If not provided, + :attr:`namespace` is set to `__main__.__dict__`. + + Completer objects have the following method: - -.. method:: Completer.complete(text, state) +.. method:: Completer.complete(text, state, use_builtins=True) Return the *state*\ th completion for *text*. - If called for *text* that doesn't include a period character (``'.'``), it will - complete from names currently defined in :mod:`__main__`, :mod:`builtins` and - keywords (as defined by the :mod:`keyword` module). + :meth:`complete` will search from names currently defined in :attr:`namespace`, + set during initialization. If *use_builtins* is :const:`True`, searches also + through the :meth:`builtins` namespace. - If called for a dotted name, it will try to evaluate anything without obvious - side-effects (functions will not be evaluated, but it can generate calls to - :meth:`__getattr__`) up to the last part, and find matches for the rest via the - :func:`dir` function. Any exception raised during the evaluation of the - expression is caught, silenced and :const:`None` is returned. + If *text* contains a dotted name, :meth:`complete` will try to evaluate + anything without obvious side-effects (functions will not be evaluated, but + it can generate calls to :meth:`__getattr__`) up to the last part, and find + matches for the rest via the :func:`dir` function. Any exception raised + during the evaluation of the expression is caught, silenced and :const:`None` + is returned. diff -r 84c8127453cf Lib/rlcompleter.py --- a/Lib/rlcompleter.py Sat Oct 06 03:40:10 2012 +0200 +++ b/Lib/rlcompleter.py Sun Oct 14 22:55:53 2012 +0200 @@ -62,7 +62,7 @@ self.use_main_ns = 0 self.namespace = namespace - def complete(self, text, state): + def complete(self, text, state, use_builtins=True): """Return the next possible completion for 'text'. This is called successively with state == 0, 1, 2, ... until it @@ -74,9 +74,9 @@ if state == 0: if "." in text: - self.matches = self.attr_matches(text) + self.matches = self.attr_matches(text, use_builtins) else: - self.matches = self.global_matches(text) + self.matches = self.global_matches(text, use_builtins) try: return self.matches[state] except IndexError: @@ -87,7 +87,7 @@ word = word + "(" return word - def global_matches(self, text): + def global_matches(self, text, use_builtins=True): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently @@ -100,13 +100,17 @@ for word in keyword.kwlist: if word[:n] == text: matches.append(word) - for nspace in [builtins.__dict__, self.namespace]: + + nspaces = [self.namespace] + if use_builtins: + nspaces.append(builtins.__dict__) + for nspace in nspaces: for word, val in nspace.items(): if word[:n] == text and word != "__builtins__": matches.append(self._callable_postfix(val, word)) return matches - def attr_matches(self, text): + def attr_matches(self, text, use_builtins=True): """Compute matches when text contains a dot. Assuming the text is of the form NAME.NAME....[NAME], and is @@ -118,14 +122,27 @@ with a __getattr__ hook is evaluated. """ - import re - m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) - if not m: + base, *attrs = text.split('.') + + if not base or not attrs or any(not x for x in attrs): return [] - expr, attr = m.group(1, 3) + + if base in self.namespace: + thisobject = self.namespace[base] + elif use_builtins and base in builtins.__dict__: + thisobject = builtins.__dict__[base] + else: + return [] + + # Go deeply through object try: - thisobject = eval(expr, self.namespace) - except Exception: + nextattr = attrs.pop(0) + while attrs: + thisobject = getattr(thisobject, nextattr) + base += '.' + nextattr + nextattr = attrs.pop(0) + attr = nextattr + except AttributeError: return [] # get the content of the object, except __builtins__ @@ -141,7 +158,7 @@ for word in words: if word[:n] == attr and hasattr(thisobject, word): val = getattr(thisobject, word) - word = self._callable_postfix(val, "%s.%s" % (expr, word)) + word = self._callable_postfix(val, "%s.%s" % (base, word)) matches.append(word) return matches diff -r 84c8127453cf Lib/test/test_rlcompleter.py --- a/Lib/test/test_rlcompleter.py Sat Oct 06 03:40:10 2012 +0200 +++ b/Lib/test/test_rlcompleter.py Sun Oct 14 22:55:53 2012 +0200 @@ -42,7 +42,6 @@ ['CompleteMe(']) self.assertEqual(self.completer.global_matches('eg'), ['egg(']) - # XXX: see issue5256 self.assertEqual(self.completer.global_matches('CompleteM'), ['CompleteMe(']) @@ -65,6 +64,26 @@ ['egg.{}('.format(x) for x in dir(str) if x.startswith('s')]) + def test_use_builtins(self): + """ Provide a set of tests for use_builtins option in attr_matches and + global_matches. See issue5256. """ + + # tests with global_matches + self.assertEqual(self.completer.global_matches('int'), ['int(']) + res = self.completer.global_matches('eva', use_builtins=False) + self.assertNotEqual(res, ['eval(']) + + # tests with attr_matches + self.assertNotEqual(self.completer.attr_matches('str.s', False), + ['str.{}('.format(x) for x in dir(str) + if x.startswith('s')]) + self.assertEqual(self.completer.attr_matches('int.deno'), + ['int.denominator']) + self.assertEqual(self.completer.attr_matches('.spam.f', False), []) + self.assertEqual(self.completer.attr_matches('foo.bar.', True), []) + self.assertEqual(self.stdcompleter.attr_matches('int.egg.spam'), []) + self.assertEqual(self.completer.attr_matches(''), []) + def test_main(): support.run_unittest(TestRlcompleter)