| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  | # Copyright (c) 2009 www.stani.be (GPL license) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | # it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | # the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | # (at your option) any later version. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | # GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-13 14:38:30 +00:00
										 |  |  | # <pep8-80 compliant> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  | import inspect | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # regular expression constants | 
					
						
							|  |  |  | DEF_DOC = '%s\s*(\(.*?\))' | 
					
						
							|  |  |  | DEF_SOURCE = 'def\s+%s\s*(\(.*?\)):' | 
					
						
							|  |  |  | RE_EMPTY_LINE = re.compile('^\s*\n') | 
					
						
							|  |  |  | RE_FLAG = re.MULTILINE | re.DOTALL | 
					
						
							|  |  |  | RE_NEWLINE = re.compile('\n+') | 
					
						
							|  |  |  | RE_SPACE = re.compile('\s+') | 
					
						
							|  |  |  | RE_DEF_COMPLETE = re.compile( | 
					
						
							|  |  |  |     # don't start with a quote | 
					
						
							|  |  |  |     '''(?:^|[^"'a-zA-Z0-9_])''' | 
					
						
							|  |  |  |     # start with a \w = [a-zA-Z0-9_] | 
					
						
							|  |  |  |     '''((\w+''' | 
					
						
							|  |  |  |     # allow also dots and closed bracket pairs [] | 
					
						
							|  |  |  |     '''(?:\w|[.]|\[.+?\])*''' | 
					
						
							|  |  |  |     # allow empty string | 
					
						
							|  |  |  |     '''|)''' | 
					
						
							|  |  |  |     # allow opening bracket(s) | 
					
						
							|  |  |  |     '''(?:\(|\s)*)$''') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def reduce_newlines(text): | 
					
						
							|  |  |  |     """Reduces multiple newlines to a single newline.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param text: text with multiple newlines | 
					
						
							|  |  |  |     :type text: str | 
					
						
							|  |  |  |     :returns: text with single newlines | 
					
						
							|  |  |  |     :rtype: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> reduce_newlines('hello\\n\\nworld') | 
					
						
							|  |  |  |     'hello\\nworld' | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     return RE_NEWLINE.sub('\n', text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def reduce_spaces(text): | 
					
						
							|  |  |  |     """Reduces multiple whitespaces to a single space.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param text: text with multiple spaces | 
					
						
							|  |  |  |     :type text: str | 
					
						
							|  |  |  |     :returns: text with single spaces | 
					
						
							|  |  |  |     :rtype: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> reduce_spaces('hello    \\nworld') | 
					
						
							|  |  |  |     'hello world' | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     return RE_SPACE.sub(' ', text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_doc(object): | 
					
						
							|  |  |  |     """Get the doc string or comments for an object.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param object: object | 
					
						
							|  |  |  |     :returns: doc string | 
					
						
							|  |  |  |     :rtype: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> get_doc(abs) | 
					
						
							|  |  |  |     'abs(number) -> number\\n\\nReturn the absolute value of the argument.' | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     result = inspect.getdoc(object) or inspect.getcomments(object) | 
					
						
							|  |  |  |     return result and RE_EMPTY_LINE.sub('', result.rstrip()) or '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_argspec(func, strip_self=True, doc=None, source=None): | 
					
						
							|  |  |  |     """Get argument specifications.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param strip_self: strip `self` from argspec | 
					
						
							|  |  |  |     :type strip_self: bool | 
					
						
							|  |  |  |     :param doc: doc string of func (optional) | 
					
						
							|  |  |  |     :type doc: str | 
					
						
							|  |  |  |     :param source: source code of func (optional) | 
					
						
							|  |  |  |     :type source: str | 
					
						
							|  |  |  |     :returns: argument specification | 
					
						
							|  |  |  |     :rtype: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> get_argspec(inspect.getclasstree) | 
					
						
							|  |  |  |     '(classes, unique=0)' | 
					
						
							|  |  |  |     >>> get_argspec(abs) | 
					
						
							|  |  |  |     '(number)' | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # get the function object of the class | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         func = func.__func__ | 
					
						
							|  |  |  |     except AttributeError: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             # py 2.X | 
					
						
							|  |  |  |             func = func.im_func | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  |     # is callable? | 
					
						
							|  |  |  |     if not hasattr(func, '__call__'): | 
					
						
							|  |  |  |         return '' | 
					
						
							|  |  |  |     # func should have a name | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         func_name = func.__name__ | 
					
						
							|  |  |  |     except AttributeError: | 
					
						
							|  |  |  |         return '' | 
					
						
							|  |  |  |     # from docstring | 
					
						
							|  |  |  |     if doc is None: | 
					
						
							|  |  |  |         doc = get_doc(func) | 
					
						
							|  |  |  |     match = re.search(DEF_DOC % func_name, doc, RE_FLAG) | 
					
						
							|  |  |  |     # from source code | 
					
						
							|  |  |  |     if not match: | 
					
						
							|  |  |  |         if source is None: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 source = inspect.getsource(func) | 
					
						
							| 
									
										
										
										
											2010-07-14 14:56:33 +00:00
										 |  |  |             except (TypeError, IOError): | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  |                 source = '' | 
					
						
							|  |  |  |         if source: | 
					
						
							|  |  |  |             match = re.search(DEF_SOURCE % func_name, source, RE_FLAG) | 
					
						
							|  |  |  |     if match: | 
					
						
							|  |  |  |         argspec = reduce_spaces(match.group(1)) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         # try with the inspect.getarg* functions | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             argspec = inspect.formatargspec(*inspect.getfullargspec(func)) | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 # py 2.X | 
					
						
							|  |  |  |                 argspec = inspect.formatargspec(*inspect.getargspec(func)) | 
					
						
							|  |  |  |             except: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     argspec = inspect.formatargvalues( | 
					
						
							|  |  |  |                         *inspect.getargvalues(func)) | 
					
						
							|  |  |  |                 except: | 
					
						
							|  |  |  |                     argspec = '' | 
					
						
							|  |  |  |         if strip_self: | 
					
						
							|  |  |  |             argspec = argspec.replace('self, ', '') | 
					
						
							|  |  |  |     return argspec | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def complete(line, cursor, namespace): | 
					
						
							|  |  |  |     """Complete callable with calltip.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param line: incomplete text line | 
					
						
							|  |  |  |     :type line: str | 
					
						
							|  |  |  |     :param cursor: current character position | 
					
						
							|  |  |  |     :type cursor: int | 
					
						
							|  |  |  |     :param namespace: namespace | 
					
						
							|  |  |  |     :type namespace: dict | 
					
						
							|  |  |  |     :returns: (matches, world, scrollback) | 
					
						
							|  |  |  |     :rtype: (list of str, str, str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> import os | 
					
						
							|  |  |  |     >>> complete('os.path.isdir(', 14, {'os': os})[-1] | 
					
						
							|  |  |  |     'isdir(s)\\nReturn true if the pathname refers to an existing directory.' | 
					
						
							|  |  |  |     >>> complete('abs(', 4, {})[-1] | 
					
						
							|  |  |  |     'abs(number) -> number\\nReturn the absolute value of the argument.' | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     matches = [] | 
					
						
							| 
									
										
										
										
											2010-10-27 16:47:25 +00:00
										 |  |  |     word = '' | 
					
						
							|  |  |  |     scrollback = '' | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  |     match = RE_DEF_COMPLETE.search(line[:cursor]) | 
					
						
							| 
									
										
										
										
											2010-10-27 16:47:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  |     if match: | 
					
						
							|  |  |  |         word = match.group(1) | 
					
						
							|  |  |  |         func_word = match.group(2) | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             func = eval(func_word, namespace) | 
					
						
							|  |  |  |         except Exception: | 
					
						
							|  |  |  |             func = None | 
					
						
							| 
									
										
										
										
											2010-10-27 16:47:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  |         if func: | 
					
						
							|  |  |  |             doc = get_doc(func) | 
					
						
							|  |  |  |             argspec = get_argspec(func, doc=doc) | 
					
						
							|  |  |  |             scrollback = func_word.split('.')[-1] + (argspec or '()') | 
					
						
							|  |  |  |             if doc.startswith(scrollback): | 
					
						
							|  |  |  |                 scrollback = doc | 
					
						
							|  |  |  |             elif doc: | 
					
						
							|  |  |  |                 scrollback += '\n' + doc | 
					
						
							|  |  |  |             scrollback = reduce_newlines(scrollback) | 
					
						
							| 
									
										
										
										
											2010-10-27 16:47:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-07 14:17:49 +00:00
										 |  |  |     return matches, word, scrollback |