File: sourcetools.py

package info (click to toggle)
pypy 5.6.0%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 97,040 kB
  • ctags: 185,069
  • sloc: python: 1,147,862; ansic: 49,642; cpp: 5,245; asm: 5,169; makefile: 529; sh: 481; xml: 232; lisp: 45
file content (296 lines) | stat: -rw-r--r-- 10,132 bytes parent folder | download | duplicates (4)
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
# a couple of support functions which
# help with generating Python source.

# XXX This module provides a similar, but subtly different, functionality
# XXX several times over, which used to be scattered over four modules.
# XXX We should try to generalize and single out one approach to dynamic
# XXX code compilation.

import sys, os, inspect, types
import py

def render_docstr(func, indent_str='', closing_str=''):
    """ Render a docstring as a string of lines.
        The argument is either a docstring or an object.
        Note that we don't use a sequence, since we want
        the docstring to line up left, regardless of
        indentation. The shorter triple quotes are
        choosen automatically.
        The result is returned as a 1-tuple."""
    if not isinstance(func, str):
        doc = func.__doc__
    else:
        doc = func
    if doc is None:
        return None
    doc = doc.replace('\\', r'\\')
    compare = []
    for q in '"""', "'''":
        txt = indent_str + q + doc.replace(q[0], "\\"+q[0]) + q + closing_str
        compare.append(txt)
    doc, doc2 = compare
    doc = (doc, doc2)[len(doc2) < len(doc)]
    return doc


class NiceCompile(object):
    """ Compiling parameterized strings in a way that debuggers
        are happy. We provide correct line numbers and a real
        __file__ attribute.
    """
    def __init__(self, namespace_or_filename):
        if type(namespace_or_filename) is str:
            srcname = namespace_or_filename
        else:
            srcname = namespace_or_filename.get('__file__')
        if not srcname:
            # assume the module was executed from the
            # command line.
            srcname = os.path.abspath(sys.argv[-1])
        self.srcname = srcname
        if srcname.endswith('.pyc') or srcname.endswith('.pyo'):
            srcname = srcname[:-1]
        if os.path.exists(srcname):
            self.srcname = srcname
            self.srctext = file(srcname).read()
        else:
            # missing source, what to do?
            self.srctext = None

    def __call__(self, src, args=None):
        """ instance NiceCompile (src, args) -- formats src with args
            and returns a code object ready for exec. Instead of <string>,
            the code object has correct co_filename and line numbers.
            Indentation is automatically corrected.
        """
        if self.srctext:
            try:
                p = self.srctext.index(src)
            except ValueError:
                msg = "Source text not found in %s - use a raw string" % self.srcname
                raise ValueError(msg)
            prelines = self.srctext[:p].count("\n") + 1
        else:
            prelines = 0
        # adjust indented def
        for line in src.split('\n'):
            content = line.strip()
            if content and not content.startswith('#'):
                break
        # see if first line is indented
        if line and line[0].isspace():
            # fake a block
            prelines -= 1
            src = 'if 1:\n' + src
        if args is not None:
            src = '\n' * prelines + src % args
        else:
            src = '\n' * prelines + src
        c = compile(src, self.srcname, "exec")
        # preserve the arguments of the code in an attribute
        # of the code's co_filename
        if self.srcname:
            srcname = MyStr(self.srcname)
            if args is not None:
                srcname.__sourceargs__ = args
            c = newcode_withfilename(c, srcname)
        return c

def getsource(object):
    """ similar to inspect.getsource, but trying to
    find the parameters of formatting generated methods and
    functions.
    """
    name = inspect.getfile(object)
    if hasattr(name, '__source__'):
        src = str(name.__source__)
    else:
        try:
            src = inspect.getsource(object)
        except Exception:   # catch IOError, IndentationError, and also rarely
            return None     # some other exceptions like IndexError
    if hasattr(name, "__sourceargs__"):
        return src % name.__sourceargs__
    return src

## the following is stolen from py.code.source.py for now.
## XXX discuss whether and how to put this functionality
## into py.code.source.
#
# various helper functions
#
class MyStr(str):
    """ custom string which allows adding attributes. """

def newcode(fromcode, **kwargs):
    names = [x for x in dir(fromcode) if x[:3] == 'co_']
    for name in names:
        if name not in kwargs:
            kwargs[name] = getattr(fromcode, name)
    return types.CodeType(
             kwargs['co_argcount'],
             kwargs['co_nlocals'],
             kwargs['co_stacksize'],
             kwargs['co_flags'],
             kwargs['co_code'],
             kwargs['co_consts'],
             kwargs['co_names'],
             kwargs['co_varnames'],
             kwargs['co_filename'],
             kwargs['co_name'],
             kwargs['co_firstlineno'],
             kwargs['co_lnotab'],
             kwargs['co_freevars'],
             kwargs['co_cellvars'],
    )

def newcode_withfilename(co, co_filename):
    newconstlist = []
    cotype = type(co)
    for c in co.co_consts:
        if isinstance(c, cotype):
            c = newcode_withfilename(c, co_filename)
        newconstlist.append(c)
    return newcode(co, co_consts = tuple(newconstlist),
                       co_filename = co_filename)

# ____________________________________________________________

import __future__

def compile2(source, filename='', mode='exec', flags=
             __future__.generators.compiler_flag, dont_inherit=0):
    """
    A version of compile() that caches the code objects it returns.
    It uses py.code.compile() to allow the source to be displayed in tracebacks.
    """
    key = (source, filename, mode, flags)
    try:
        co = compile2_cache[key]
        #print "***** duplicate code ******* "
        #print source
    except KeyError:
        #if DEBUG:
        co = py.code.compile(source, filename, mode, flags)
        #else:
        #    co = compile(source, filename, mode, flags)
        compile2_cache[key] = co
    return co

compile2_cache = {}

# ____________________________________________________________

def compile_template(source, resultname):
    """Compiles the source code (a string or a list/generator of lines)
    which should be a definition for a function named 'resultname'.
    The caller's global dict and local variable bindings are captured.
    """
    if not isinstance(source, py.code.Source):
        if isinstance(source, str):
            lines = [source]
        else:
            lines = list(source)
        lines.append('')
        source = py.code.Source('\n'.join(lines))

    caller = sys._getframe(1)
    locals = caller.f_locals
    if locals is caller.f_globals:
        localnames = []
    else:
        localnames = locals.keys()
        localnames.sort()
    values = [locals[key] for key in localnames]

    source = source.putaround(
        before = "def container(%s):" % (', '.join(localnames),),
        after  = "# no unindent\n    return %s" % resultname)

    d = {}
    exec source.compile() in caller.f_globals, d
    container = d['container']
    return container(*values)

# ____________________________________________________________

def func_with_new_name(func, newname, globals=None):
    """Make a renamed copy of a function."""
    if globals is None:
        globals = func.func_globals
    f = types.FunctionType(func.func_code, globals, newname,
            func.func_defaults, func.func_closure)
    if func.func_dict:
        f.func_dict = {}
        f.func_dict.update(func.func_dict)
    f.func_doc = func.func_doc
    return f

def func_renamer(newname):
    """A function decorator which changes the name of a function."""
    def decorate(func):
        return func_with_new_name(func, newname)
    return decorate

PY_IDENTIFIER = ''.join([(('0' <= chr(i) <= '9' or
                           'a' <= chr(i) <= 'z' or
                           'A' <= chr(i) <= 'Z') and chr(i) or '_')
                         for i in range(256)])
PY_IDENTIFIER_MAX = 120

def valid_identifier(stuff):
    stuff = str(stuff).translate(PY_IDENTIFIER)
    if not stuff or ('0' <= stuff[0] <= '9'):
        stuff = '_' + stuff
    return stuff[:PY_IDENTIFIER_MAX]

CO_VARARGS      = 0x0004
CO_VARKEYWORDS  = 0x0008

def has_varargs(func):
    func = getattr(func, 'func_code', func)
    return (func.co_flags & CO_VARARGS) != 0

def has_varkeywords(func):
    func = getattr(func, 'func_code', func)
    return (func.co_flags & CO_VARKEYWORDS) != 0

def nice_repr_for_func(fn, name=None):
    mod = getattr(fn, '__module__', None)
    if name is None:
        name = getattr(fn, '__name__', None)
        cls = getattr(fn, 'class_', None)
        if name is not None and cls is not None:
            name = "%s.%s" % (cls.__name__, name)
    try:
        firstlineno = fn.func_code.co_firstlineno
    except AttributeError:
        firstlineno = -1
    return "(%s:%d)%s" % (mod or '?', firstlineno, name or 'UNKNOWN')


def rpython_wrapper(f, template, templateargs=None, **globaldict):
    """
    We cannot simply wrap the function using *args, **kwds, because it's not
    RPython. Instead, we generate a function from ``template`` with exactly
    the same argument list.
    """
    if templateargs is None:
        templateargs = {}
    srcargs, srcvarargs, srckeywords, defaults = inspect.getargspec(f)
    assert not srcvarargs, '*args not supported by rpython_wrapper'
    assert not srckeywords, '**kwargs not supported by rpython_wrapper'
    #
    arglist = ', '.join(srcargs)
    templateargs.update(name=f.func_name,
                        arglist=arglist,
                        original=f.func_name+'_original')
    src = template.format(**templateargs)
    src = py.code.Source(src)
    #
    globaldict[f.func_name + '_original'] = f
    exec src.compile() in globaldict
    result = globaldict[f.func_name]
    result.func_defaults = f.func_defaults
    result.func_dict.update(f.func_dict)
    return result