summaryrefslogtreecommitdiff
path: root/sandbox/tibs/pysource/utils.py
blob: 8823c9265cb57b6f1db2fdc17add4492572cfa82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
"""Utilities for pysource
"""

import types
import string
import compiler

# We'd better have at least *one* module in this package that demonstrates
# *not* using reST for our docstrings...
__docformat__ = "none"


# ----------------------------------------------------------------------
PLAINTEXT = "plaintext"
RESTRUCTUREDTEXT = "restructuredtext"

canonical_format = { "plaintext" : PLAINTEXT,
                     "plain"     : PLAINTEXT,
                     "none"      : PLAINTEXT,
                     "rst"              : RESTRUCTUREDTEXT,
                     "rest"             : RESTRUCTUREDTEXT,
                     "rtxt"             : RESTRUCTUREDTEXT,
                     "restructuredtext" : RESTRUCTUREDTEXT,
                     }

def docformat(text):
    """Interpret a module's __docformat__ string.

    Returns a tuple of (format,language)
    """
    if text == None:
        return PLAINTEXT,"en"

    words = string.split(string.lower(text))

    #print words

    if len(words) == 0:
        return PLAINTEXT,"en"
    elif len(words) > 2:
        raise ValueError,"__docformat__ may be at most two 'words'"

    if len(words) == 2:
        language = string.lower(words[1])
    else:
        language = "en"

    try:
        format = canonical_format[string.lower(words[0])]
    except KeyError:
        legal = canonical_format.keys()
        legal.sort()
        raise ValueError,"__docformat__ should be one of %s"%legal

    return format,language


# ----------------------------------------------------------------------
def flatten(item):
    """Retrieve some simpler representation of our AST.

    (and it's not meant to be 'theoretically' wonderful, just something
    I can look at to work out how an AST works...)
    """
    if isinstance(item,compiler.ast.Node):
        things = [item.__class__.__name__]
        children = item.getChildren()
        for child in children:
            things.append(flatten(child))
        return things
    else:
        return [item]


# ----------------------------------------------------------------------
def treeprint(stream,item,indent=0):
    """Simple pretty printer for the AST."""
    if isinstance(item,compiler.ast.Node):
        stream.write("\n%s<%s>"%(" "*indent,item.__class__.__name__))

        children = item.getChildren()
        for child in children:
            treeprint(stream,child,indent+2)

        # Fake our docstring as a sub-node (it's *really* more an attribute)
        if hasattr(item,"docstring"):
            stream.write("\n%s  <docstring> %s"%(" "*indent,item.docstring))

        # And ditto for a remembered assignment expression
        if hasattr(item,"assign_expr"):
            stream.write("\n%s  <assign_expr>"%(" "*indent))
            treeprint(stream,item.assign_expr,indent+4)
    else:
        stream.write(" ")
        stream.write(`item`)


# ----------------------------------------------------------------------
def find_attr_docs(tree,verbose=0):
    """Find candidates for documented attributes

    Note that after this, it may be that the AST will not garbage collect
    its own nodes properly anymore, as we are adding in cross-linkages.
    """

    if not isinstance(tree,compiler.ast.Node):
        return

    children = tree.getChildren()

    # Might as well get our recursion done with first...
    for child in children:
        find_attr_docs(child,verbose)

    # I believe that only Stmt nodes can have Assign and Discard
    # nodes as children
    if not isinstance(tree,compiler.ast.Stmt):
        return

    if len(children) == 0:
        return

    pairs = []
    last  = children[0]
    for item in children[1:]:
        pairs.append((last,item))
        last = item

    for this,next in pairs:
        if isinstance(this,compiler.ast.Assign) and \
           isinstance(next,compiler.ast.Discard):
            if verbose:
                print
                print
                print "*** Attribute docstring candidate"
                treeprint(this,4)
                treeprint(next,4)
                print

            nextexpr = next.expr
            if isinstance(nextexpr,compiler.ast.Const):
                if type(nextexpr.value) == types.StringType:
                    docstring = nextexpr.value
                else:
                    if verbose:
                        print
                        print "... Discarded constant is not a string"
                    continue
            else:
                if verbose:
                    print
                    print "... Discarded expression is not a constant"
                continue

            # If there is more than one assignment attached to
            # the <Assign> node, we are not interested
            if len(this.nodes) > 1:
                if verbose:
                    print
                    print "... (but there are too many assignments in the <Assign>)"
                continue

            target = this.nodes[0]
            if isinstance(target,compiler.ast.AssName):
                # Let's be cheeky and glue the docstring on...
                target.docstring = docstring
            elif isinstance(target,compiler.ast.AssAttr):
                # Let's be cheeky and glue the docstring on...
                target.docstring = docstring
            else:
                if verbose:
                    print
                    print "... (but the assignment is to a tuple/list/etc.)"
                continue

            if verbose:
                print
                print "Becomes:"
                treeprint(this,4)


# ----------------------------------------------------------------------
def find_attr_vals(tree,verbose=0):
    """Find attributes whose values we're interested in.

    Clearly, when this is working, it could do with being "folded" into
    `find_attr_docs()`.

    Note that after this, it may be that the AST will not garbage collect
    its own nodes properly anymore, as we are adding in cross-linkages.
    """

    if not isinstance(tree,compiler.ast.Node):
        return

    children = tree.getChildren()

    # Might as well get our recursion done with first...
    for child in children:
        find_attr_vals(child,verbose)

    # I believe that only Stmt nodes can have Assign and Discard
    # nodes as children
    if not isinstance(tree,compiler.ast.Stmt):
        return

    for this in children:
        if isinstance(this,compiler.ast.Assign):
            if verbose:
                print
                print
                print "*** Assignment - name/value candidate"
                treeprint(this,4)
                print

            # If there is more than one assignment attached to
            # the <Assign> node, we are not interested
            if len(this.nodes) > 1:
                if verbose:
                    print
                    print "... (but there are too many assignments in the <Assign>)"
                continue

            target = this.nodes[0]
            if isinstance(target,compiler.ast.AssName) or \
               isinstance(target,compiler.ast.AssAttr):
                # Let's be cheeky and glue the associated expression on...
                target.assign_expr = this.expr
            else:
                if verbose:
                    print
                    print "... (but the assignment is to a tuple/list/etc.)"
                continue

            if verbose:
                print
                print "Becomes:"
                treeprint(this,4)


# ----------------------------------------------------------------------
def stringify_arg(thing):
    """Return a string representation of a function argument.

    This just works for tuples of (strings or tuples (of strings ...) ...)
    """
    if type(thing) == types.StringType:
        return thing
    elif type(thing) == types.TupleType:
        innards = []
        for item in thing:
            innards.append(stringify_arg(item))
        return "(" + string.join(innards,",") + ")"
    else:
        raise ValueError,"Tried to stringify type %s"%type(thing)


# ----------------------------------------------------------------------
def merge_args(args,defaults):
    """Merge together arguments and defaults from an argument list.

    Returns a list of argument strings.
    """
    if args == None:
        return []

    if defaults == None:
        defaults = []

    # This is horrible - do it nicely later on!
    argstrs = []
    for item in args:
        argstrs.append(stringify_arg(item))

    pos = len(args) - len(defaults)
    next = 0
    for index in range(pos,len(args)):
        thing = defaults[next]
        thing = stringify_expr(thing)
        argstrs[index] = "%s=%s"%(argstrs[index],thing)
        next = next + 1
    return argstrs


# ----------------------------------------------------------------------
def stringify_expr(thing):
    """Return a very simple string representation of an expression node

    Specifically, this function aims to support stringifying things
    which can be on the RHS of an assignment - is that *actually* the
    same as stringifying expression nodes?
    """

    # Humph - saving typing may be a good thing...
    strify = stringify_expr

    #print thing.__class__.__name__
    
    if thing == None:
        return 'None'
    elif isinstance(thing,compiler.ast.Add):
        return strify(thing.left) + " + " + strify(thing.right)
    elif isinstance(thing,compiler.ast.And):
        exprs = []
        for node in thing.nodes:
            exprs.append(strify(node))
        return string.join(exprs," && ")
    elif isinstance(thing,compiler.ast.AssAttr):
        # Attribute as target of assignment
        if thing.flags == compiler.consts.OP_ASSIGN:
            return strify(thing.expr) + "." + thing.attrname
        else:
            raise ValueError,"Unexpected flag %d in %s"%(thing.flags,`thing`)
    elif isinstance(thing,compiler.ast.AssName):
        # Name as target of assignment, but this also means name
        # as target of "in" assignment (e.g., "x in [1,2,3]"),
        # which is why *we're* interested in it (since this can
        # occur in list comprehensions, which can occur as the
        # RHS of assignments)
        if thing.flags == compiler.consts.OP_ASSIGN:
            return thing.name
        else:
            raise ValueError,"Unexpected flag %d in %s"%(thing.flags,`thing`)
    elif isinstance(thing,compiler.ast.Backquote):
        return "`" + strify(thing.expr) + "`"
    elif isinstance(thing,compiler.ast.Bitand):
        exprs = []
        for node in thing.nodes:
            exprs.append(strify(node))
        return string.join(exprs," & ")
    elif isinstance(thing,compiler.ast.Bitor):
        exprs = []
        for node in thing.nodes:
            exprs.append(strify(node))
        return string.join(exprs," | ")
    elif isinstance(thing,compiler.ast.Bitxor):
        exprs = []
        for node in thing.nodes:
            exprs.append(strify(node))
        return string.join(exprs," ^ ")
    elif isinstance(thing,compiler.ast.CallFunc):
        # Yuck - this is getting complicated!
        # (for an example, see method `hyperlink_target` in
        # restructuredtext/states.py)
        str = strify(thing.node) + "("
        arglist = []
        if thing.args:
            for arg in thing.args:
                arglist.append(strify(arg))
        if thing.star_args:
            arglist.append("*"+strify(thing.star_args))
        if thing.dstar_args:
            arglist.append("**"+strify(thing.dstar_args))
        if arglist:
            str += string.join(arglist,", ")
        return str+")"
    elif isinstance(thing,compiler.ast.Compare):
        str = strify(thing.expr) + " "
        for op,val in thing.ops:
            str += op + " " + strify(val)
        return str
    elif isinstance(thing,compiler.ast.Const):
        # Try not to let long strings take up too much room...
        value = thing.value
        if type(value) == type("") and len(value) > 50:
            value = value[:47] + "..."
        # Make Python decide for us if it needs quotes round it
        # (and, if so, what sort)
        return `value`
    elif isinstance(thing,compiler.ast.Dict):
        innards = []
        for key,val in thing.items:
            key = strify(key)
            val = strify(val)
            innards.append(key+":"+val)
        return "{" + string.join(innards,", ") + "}"
    elif isinstance(thing,compiler.ast.Div):
        return strify(thing.left) + " / " + strify(thing.right)
    elif isinstance(thing,compiler.ast.Ellipsis):
        return "..."
    elif isinstance(thing,compiler.ast.Getattr):
        return strify(thing.expr) + "." + thing.attrname
    elif isinstance(thing,compiler.ast.Invert):
        # Bitwise negation
        return "~" + strify(thing.expr)
    elif isinstance(thing,compiler.ast.Keyword):
        # An 'arg=value' within a function call
        return thing.name + "=" + strify(thing.expr)
    elif isinstance(thing,compiler.ast.Lambda):
        str = "lambda "
        if thing.flags != 0:
            str += " <flag %d> "%thing.flags
        str += string.join(merge_args(thing.argnames,thing.defaults),", ")
        str += ": "
        str += strify(thing.code)
        return str
    elif isinstance(thing,compiler.ast.LeftShift):
        return strify(thing.left) + " << " + strify(thing.right)
    elif isinstance(thing,compiler.ast.List):
        innards = []
        for item in thing.nodes:
            innards.append(strify(item))
        return "[" + string.join(innards,", ") + "]"
    elif isinstance(thing,compiler.ast.ListComp):
        str = "["+strify(thing.expr)
        for node in thing.quals:
            str += " "+strify(node)
        return str+"]"
    elif isinstance(thing,compiler.ast.ListCompFor):
        str = "for "+strify(thing.assign)
        str += " in "+strify(thing.list)
        if thing.ifs:
            for node in thing.ifs:
                str += " "+strify(node)
        return str
    elif isinstance(thing,compiler.ast.ListCompIf):
        return "if "+strify(thing.test)
    elif isinstance(thing,compiler.ast.Mod):
        return strify(thing.left) + "%" + strify(thing.right)
    elif isinstance(thing,compiler.ast.Mul):
        return strify(thing.left) + " * " + strify(thing.right)
    elif isinstance(thing,compiler.ast.Name):
        return thing.name
    elif isinstance(thing,compiler.ast.Not):
        return "not " + strify(thing.expr)
    elif isinstance(thing,compiler.ast.Or):
        exprs = []
        for node in thing.nodes:
            exprs.append(strify(node))
        return string.join(exprs," || ")
    elif isinstance(thing,compiler.ast.Power):
        return strify(thing.left) + " ** " + strify(thing.right)
    elif isinstance(thing,compiler.ast.RightShift):
        return strify(thing.left) + " >> " + strify(thing.right)
    elif isinstance(thing,compiler.ast.Slice):
        if thing.flags != compiler.consts.OP_APPLY:
            raise ValueError,"Unexpected flag %d in %s"%(thing.flags,`thing`)
        return strify(thing.expr) + "[" + \
               strify(thing.lower) + ":" + strify(thing.upper) + "]"
    elif isinstance(thing,compiler.ast.Sliceobj):
        slicelist = []
        for idx in thing.nodes:
            slicelist.append(strify(idx))
        return string.join(slicelist,":")
    elif isinstance(thing,compiler.ast.Sub):
        return strify(thing.left) + " - " + strify(thing.right)
    elif isinstance(thing,compiler.ast.Subscript):
        if thing.flags != compiler.consts.OP_APPLY:
            raise ValueError,"Unexpected flag %d in %s"%(thing.flags,`thing`)
        str = strify(thing.expr) + "["
        sublist = []
        for sub in thing.subs:
            sublist.append(strify(sub))
        return str + string.join(sublist,", ") + "]"
    elif isinstance(thing,compiler.ast.Tuple):
        innards = []
        for item in thing.nodes:
            innards.append(strify(item))
        return "(" + string.join(innards,", ") + ")"
    elif isinstance(thing,compiler.ast.UnaryAdd):
        return "+" + strify(thing.expr)
    elif isinstance(thing,compiler.ast.UnarySub):
        return "-" + strify(thing.expr)
    else:
        return _whatsthis(thing)

def _whatsthis(thing):
    # Wrong, but what else can we do?
    import sys
    print >>sys.stderr,"stringify_expr - don't recognise %s %s"%\
          (thing.__class__.__name__,thing)
    return `thing`