Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / console / introspect.py @ 462

History | View | Annotate | Download (13.8 KB)

1
"""Provides a variety of introspective-type support functions for
2
things like call tips and command auto completion.
3

4
NOTE: this file is a modification of Patrick O'Brien's version 1.62
5
"""
6

    
7
#from __future__ import nested_scopes
8

    
9
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
10
__cvsid__ = "$Id: introspect.py 5910 2006-06-20 10:03:31Z jmvivo $"
11
__revision__ = "$Revision: 5910 $"[11:-2]
12

    
13

    
14
import cStringIO
15
import inspect
16
import sys
17
import tokenize
18
import types
19

    
20
try:
21
    True
22
except NameError:
23
    True = 1==1
24
    False = 1==0
25

    
26
def getAutoCompleteList(command='', locals=None, includeMagic=1, 
27
                        includeSingle=1, includeDouble=1):
28
    """Return list of auto-completion options for command.
29
    
30
    The list of options will be based on the locals namespace."""
31
    attributes = []
32
    # Get the proper chunk of code from the command.
33
    root = getRoot(command, terminator='.')
34
    try:
35
        if locals is not None:
36
            object = eval(root, locals)
37
        else:
38
            object = eval(root)
39
    except:
40
        pass
41
    else:
42
        attributes = getAttributeNames(object, includeMagic, 
43
                                       includeSingle, includeDouble)
44
    return attributes
45
    
46
def getAttributeNames(object, includeMagic=1, includeSingle=1,
47
                      includeDouble=1):
48
    """Return list of unique attributes, including inherited, for object."""
49
    attributes = []
50
    dict = {}
51
    if not hasattrAlwaysReturnsTrue(object):
52
        # Add some attributes that don't always get picked up.  If
53
        # they don't apply, they'll get filtered out at the end.
54
        attributes += ['__bases__', '__class__', '__dict__', '__name__', 
55
                       'func_closure', 'func_code', 'func_defaults', 
56
                       'func_dict', 'func_doc', 'func_globals', 'func_name']
57
    if includeMagic:
58
        try: attributes += object._getAttributeNames()
59
        except: pass
60
    # Get all attribute names.
61
    attrdict = getAllAttributeNames(object)
62
    for attrlist in attrdict.values():
63
        attributes += attrlist
64
    # Remove duplicates from the attribute list.
65
    for item in attributes:
66
        dict[item] = None
67
    attributes = dict.keys()
68
    attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
69
    if not includeSingle:
70
        attributes = filter(lambda item: item[0]!='_' \
71
                            or item[1]=='_', attributes)
72
    if not includeDouble:
73
        attributes = filter(lambda item: item[:2]!='__', attributes)
74
    # Make sure we haven't picked up any bogus attributes somehow.
75
    attributes = [attribute for attribute in attributes \
76
                  if hasattr(object, attribute)]
77
    return attributes
78

    
79
def hasattrAlwaysReturnsTrue(object):
80
    return hasattr(object, 'bogu5_123_aTTri8ute')
81

    
82
def getAllAttributeNames(object):
83
    """Return dict of all attributes, including inherited, for an object.
84
    
85
    Recursively walk through a class and all base classes.
86
    """
87
    #print "getAllAttributeNames  1",repr(object), repr(str(object))
88
    attrdict = {}  # (object, technique, count): [list of attributes]
89
    # !!!
90
    # Do Not use hasattr() as a test anywhere in this function,
91
    # because it is unreliable with remote objects: xmlrpc, soap, etc.
92
    # They always return true for hasattr().
93
    # !!!
94
    #print "getAllAttributeNames  2"
95
    try:
96
        # Yes, this can fail if object is an instance of a class with
97
        # __str__ (or __repr__) having a bug or raising an
98
        # exception. :-(
99
        key = str(object)
100
    except:
101
        key = 'anonymous'
102
    # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
103
    #print "getAllAttributeNames  3"
104
    wakeupcall = dir(object)
105
    #print "getAllAttributeNames  4"
106
    del wakeupcall
107
    # Get attributes available through the normal convention.
108
    #print "getAllAttributeNames  5"
109
    attributes = dir(object)
110
    attrdict[(key, 'dir', len(attributes))] = attributes
111
    #print "getAllAttributeNames  6"
112
    # Get attributes from the object's dictionary, if it has one.
113
    try:
114
        attributes = object.__dict__.keys()
115
        attributes.sort()
116
    except:  # Must catch all because object might have __getattr__.
117
        pass
118
    else:
119
        attrdict[(key, '__dict__', len(attributes))] = attributes
120
    # For a class instance, get the attributes for the class.
121
    #print "getAllAttributeNames  7"
122
    try:
123
        klass = object.__class__
124
    except:  # Must catch all because object might have __getattr__.
125
        pass
126
    else:
127
        if klass is object or klass is type:
128
            # Break a circular reference. This happens with extension
129
            # classes.
130
            pass
131
        else:
132
            attrdict.update(getAllAttributeNames(klass))
133
    #print "getAllAttributeNames  8"
134
    # Also get attributes from any and all parent classes.
135
    try:
136
        bases = object.__bases__
137
    except:  # Must catch all because object might have __getattr__.
138
        pass
139
    else:
140
        if isinstance(bases, types.TupleType):
141
            for base in bases:
142
                if type(base) is types.TypeType:
143
                    # Break a circular reference. Happens in Python 2.2.
144
                    pass
145
                else:
146
                    attrdict.update(getAllAttributeNames(base))
147
    #print "getAllAttributeNames  attrdict=",repr(attrdict)
148
    return attrdict
149

    
150
def getCallTip(command='', locals=None):
151
    """For a command, return a tuple of object name, argspec, tip text.
152
    
153
    The call tip information will be based on the locals namespace."""
154
    calltip = ('', '', '')  # object name, argspec, tip text.
155
    # Get the proper chunk of code from the command.
156
    root = getRoot(command, terminator='(')
157
    try:
158
        if locals is not None:
159
            object = eval(root, locals)
160
        else:
161
            object = eval(root)
162
    except:
163
        return calltip
164
    name = ''
165
    object, dropSelf = getBaseObject(object)
166
    try:
167
        name = object.__name__
168
    except AttributeError:
169
        pass
170
    tip1 = ''
171
    argspec = ''
172
    if inspect.isbuiltin(object):
173
        # Builtin functions don't have an argspec that we can get.
174
        pass
175
    elif inspect.isfunction(object):
176
        # tip1 is a string like: "getCallTip(command='', locals=None)"
177
        argspec = apply(inspect.formatargspec, inspect.getargspec(object))
178
        if dropSelf:
179
            # The first parameter to a method is a reference to an
180
            # instance, usually coded as "self", and is usually passed
181
            # automatically by Python; therefore we want to drop it.
182
            temp = argspec.split(',')
183
            if len(temp) == 1:  # No other arguments.
184
                argspec = '()'
185
            else:  # Drop the first argument.
186
                argspec = '(' + ','.join(temp[1:]).lstrip()
187
        tip1 = name + argspec
188
    doc = ''
189
    if callable(object):
190
        doc = inspect.getdoc(object)
191
    if doc:
192
        # tip2 is the first separated line of the docstring, like:
193
        # "Return call tip text for a command."
194
        # tip3 is the rest of the docstring, like:
195
        # "The call tip information will be based on ... <snip>
196
        firstline = doc.split('\n')[0].lstrip()
197
        if tip1 == firstline:
198
            tip1 = ''
199
        else:
200
            tip1 += '\n\n'
201
        docpieces = doc.split('\n\n')
202
        tip2 = docpieces[0]
203
        tip3 = '\n\n'.join(docpieces[1:])
204
        tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
205
    else:
206
        tip = tip1
207
    calltip = (name, argspec[1:-1], tip.strip())
208
    return calltip
209

    
210
def getRoot(command, terminator=None):
211
    """Return the rightmost root portion of an arbitrary Python command.
212
    
213
    Return only the root portion that can be eval()'d without side
214
    effects.  The command would normally terminate with a '(' or
215
    '.'. The terminator and anything after the terminator will be
216
    dropped."""
217
    command = command.split('\n')[-1]
218
    if command.startswith(sys.ps2):
219
        command = command[len(sys.ps2):]
220
    command = command.lstrip()
221
    command = rtrimTerminus(command, terminator)
222
    tokens = getTokens(command)
223
    if not tokens:
224
        return ''
225
    if tokens[-1][0] is tokenize.ENDMARKER:
226
        # Remove the end marker.
227
        del tokens[-1]
228
    if not tokens:
229
        return ''
230
    if terminator == '.' and \
231
           (tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
232
        # Trap decimals in numbers, versus the dot operator.
233
        return ''
234
    else:
235
        # Strip off the terminator.
236
        if terminator and command.endswith(terminator):
237
            size = 0 - len(terminator)
238
            command = command[:size]
239
    command = command.rstrip()
240
    tokens = getTokens(command)
241
    tokens.reverse()
242
    line = ''
243
    start = None
244
    prefix = ''
245
    laststring = '.'
246
    emptyTypes = ('[]', '()', '{}')
247
    for token in tokens:
248
        tokentype = token[0]
249
        tokenstring = token[1]
250
        line = token[4]
251
        if tokentype is tokenize.ENDMARKER:
252
            continue
253
        if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
254
        and laststring != '.':
255
            # We've reached something that's not part of the root.
256
            if prefix and line[token[3][1]] != ' ':
257
                # If it doesn't have a space after it, remove the prefix.
258
                prefix = ''
259
            break
260
        if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
261
        or (tokentype is tokenize.OP and tokenstring == '.'):
262
            if prefix:
263
                # The prefix isn't valid because it comes after a dot.
264
                prefix = ''
265
                break
266
            else:
267
                # start represents the last known good point in the line.
268
                start = token[2][1]
269
        elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
270
            # Remember, we're working backwords.
271
            # So prefix += tokenstring would be wrong.
272
            if prefix in emptyTypes and tokenstring in ('[({'):
273
                # We've already got an empty type identified so now we
274
                # are in a nested situation and we can break out with
275
                # what we've got.
276
                break
277
            else:
278
                prefix = tokenstring + prefix
279
        else:
280
            # We've reached something that's not part of the root.
281
            break
282
        laststring = tokenstring
283
    if start is None:
284
        start = len(line)
285
    root = line[start:]
286
    if prefix in emptyTypes:
287
        # Empty types are safe to be eval()'d and introspected.
288
        root = prefix + root
289
    return root    
290

    
291
class _EaterTokens:
292
  def __init__(self,tokens):
293
    self.tokens = tokens
294

    
295
  def __call__(self,*args):
296
    self.tokens.append(args)
297

    
298
def getTokens(command):
299
    """Return list of token tuples for command."""
300
    command = str(command)  # In case the command is unicode, which fails.
301
    f = cStringIO.StringIO(command)
302
    # tokens is a list of token tuples, each looking like: 
303
    # (type, string, (srow, scol), (erow, ecol), line)
304
    tokens = []
305
    # Can't use list comprehension:
306
    #   tokens = [token for token in tokenize.generate_tokens(f.readline)]
307
    # because of need to append as much as possible before TokenError.
308
    try:
309
##        This code wasn't backward compatible with Python 2.1.3.
310
##
311
##        for token in tokenize.generate_tokens(f.readline):
312
##            tokens.append(token)
313
        # This works with Python 2.1.3 (with nested_scopes).
314
##        def eater(*args):
315
##            tokens.append(args)
316
##        tokenize.tokenize_loop(f.readline, eater)
317
        eater = _EaterTokens(tokens) #Con una lambda no se lo come 
318
        tokenize.tokenize_loop(f.readline, eater)
319
    except tokenize.TokenError:
320
        # This is due to a premature EOF, which we expect since we are
321
        # feeding in fragments of Python code.
322
        pass
323
    return tokens    
324

    
325
def rtrimTerminus(command, terminator=None):
326
    """Return command minus anything that follows the final terminator."""
327
    if terminator:
328
        pieces = command.split(terminator)
329
        if len(pieces) > 1:
330
            command = terminator.join(pieces[:-1]) + terminator
331
    return command
332

    
333
def getBaseObject(object):
334
    """Return base object and dropSelf indicator for an object."""
335
    if inspect.isbuiltin(object):
336
        # Builtin functions don't have an argspec that we can get.
337
        dropSelf = 0
338
    elif inspect.ismethod(object):
339
        # Get the function from the object otherwise
340
        # inspect.getargspec() complains that the object isn't a
341
        # Python function.
342
        try:
343
            if object.im_self is None:
344
                # This is an unbound method so we do not drop self
345
                # from the argspec, since an instance must be passed
346
                # as the first arg.
347
                dropSelf = 0
348
            else:
349
                dropSelf = 1
350
            object = object.im_func
351
        except AttributeError:
352
            dropSelf = 0
353
    elif inspect.isclass(object):
354
        # Get the __init__ method function for the class.
355
        constructor = getConstructor(object)
356
        if constructor is not None:
357
            object = constructor
358
            dropSelf = 1
359
        else:
360
            dropSelf = 0
361
    elif callable(object):
362
        # Get the __call__ method instead.
363
        try:
364
            call_method = object.__call__.im_func
365
            if call_method.__name__ == '__call__':
366
                # unbound jython method end up here, example: string.index(
367
                dropSelf = 0
368
            else:
369
                object = call_method
370
                dropSelf = 1
371
        except AttributeError:
372
            # unbound python methods end up here
373
            dropSelf = 0
374
    else:
375
        dropSelf = 0
376
    return object, dropSelf
377

    
378
def getConstructor(object):
379
    """Return constructor for class object, or None if there isn't one."""
380
    try:
381
        return object.__init__.im_func
382
    except AttributeError:
383
        for base in object.__bases__:
384
            constructor = getConstructor(base)
385
            if constructor is not None:
386
                return constructor
387
    return None