comparison pyqver2.py @ 2:a0c85f2d74a5 draft

planemo upload for repository https://github.com/sanbi-sa/tools-sanbi-uwc commit 9612f06b8c60520dc0a047ec072ced317c7796e4
author sanbi-uwc
date Wed, 01 Feb 2017 08:45:12 -0500
parents
children
comparison
equal deleted inserted replaced
1:4b9ddf64558d 2:a0c85f2d74a5
1 #!/usr/bin/env python
2
3 import compiler
4 import platform
5 import sys
6
7 StandardModules = {
8 "__future__": (2, 1),
9 "abc": (2, 6),
10 "argparse": (2, 7),
11 "ast": (2, 6),
12 "atexit": (2, 0),
13 "bz2": (2, 3),
14 "cgitb": (2, 2),
15 "collections": (2, 4),
16 "contextlib": (2, 5),
17 "cookielib": (2, 4),
18 "cProfile": (2, 5),
19 "csv": (2, 3),
20 "ctypes": (2, 5),
21 "datetime": (2, 3),
22 "decimal": (2, 4),
23 "difflib": (2, 1),
24 "DocXMLRPCServer": (2, 3),
25 "dummy_thread": (2, 3),
26 "dummy_threading": (2, 3),
27 "email": (2, 2),
28 "fractions": (2, 6),
29 "functools": (2, 5),
30 "future_builtins": (2, 6),
31 "hashlib": (2, 5),
32 "heapq": (2, 3),
33 "hmac": (2, 2),
34 "hotshot": (2, 2),
35 "HTMLParser": (2, 2),
36 "importlib": (2, 7),
37 "inspect": (2, 1),
38 "io": (2, 6),
39 "itertools": (2, 3),
40 "json": (2, 6),
41 "logging": (2, 3),
42 "modulefinder": (2, 3),
43 "msilib": (2, 5),
44 "multiprocessing": (2, 6),
45 "netrc": (1, 5, 2),
46 "numbers": (2, 6),
47 "optparse": (2, 3),
48 "ossaudiodev": (2, 3),
49 "pickletools": (2, 3),
50 "pkgutil": (2, 3),
51 "platform": (2, 3),
52 "pydoc": (2, 1),
53 "runpy": (2, 5),
54 "sets": (2, 3),
55 "shlex": (1, 5, 2),
56 "SimpleXMLRPCServer": (2, 2),
57 "spwd": (2, 5),
58 "sqlite3": (2, 5),
59 "ssl": (2, 6),
60 "stringprep": (2, 3),
61 "subprocess": (2, 4),
62 "sysconfig": (2, 7),
63 "tarfile": (2, 3),
64 "textwrap": (2, 3),
65 "timeit": (2, 3),
66 "unittest": (2, 1),
67 "uuid": (2, 5),
68 "warnings": (2, 1),
69 "weakref": (2, 1),
70 "winsound": (1, 5, 2),
71 "wsgiref": (2, 5),
72 "xml.dom": (2, 0),
73 "xml.dom.minidom": (2, 0),
74 "xml.dom.pulldom": (2, 0),
75 "xml.etree.ElementTree": (2, 5),
76 "xml.parsers.expat":(2, 0),
77 "xml.sax": (2, 0),
78 "xml.sax.handler": (2, 0),
79 "xml.sax.saxutils": (2, 0),
80 "xml.sax.xmlreader":(2, 0),
81 "xmlrpclib": (2, 2),
82 "zipfile": (1, 6),
83 "zipimport": (2, 3),
84 "_ast": (2, 5),
85 "_winreg": (2, 0),
86 }
87
88 Functions = {
89 "all": (2, 5),
90 "any": (2, 5),
91 "collections.Counter": (2, 7),
92 "collections.defaultdict": (2, 5),
93 "collections.OrderedDict": (2, 7),
94 "enumerate": (2, 3),
95 "frozenset": (2, 4),
96 "itertools.compress": (2, 7),
97 "math.erf": (2, 7),
98 "math.erfc": (2, 7),
99 "math.expm1": (2, 7),
100 "math.gamma": (2, 7),
101 "math.lgamma": (2, 7),
102 "memoryview": (2, 7),
103 "next": (2, 6),
104 "os.getresgid": (2, 7),
105 "os.getresuid": (2, 7),
106 "os.initgroups": (2, 7),
107 "os.setresgid": (2, 7),
108 "os.setresuid": (2, 7),
109 "reversed": (2, 4),
110 "set": (2, 4),
111 "subprocess.check_call": (2, 5),
112 "subprocess.check_output": (2, 7),
113 "sum": (2, 3),
114 "symtable.is_declared_global": (2, 7),
115 "weakref.WeakSet": (2, 7),
116 }
117
118 Identifiers = {
119 "False": (2, 2),
120 "True": (2, 2),
121 }
122
123 def uniq(a):
124 if len(a) == 0:
125 return []
126 else:
127 return [a[0]] + uniq([x for x in a if x != a[0]])
128
129 class NodeChecker(object):
130 def __init__(self):
131 self.vers = dict()
132 self.vers[(2,0)] = []
133 def add(self, node, ver, msg):
134 if ver not in self.vers:
135 self.vers[ver] = []
136 self.vers[ver].append((node.lineno, msg))
137 def default(self, node):
138 for child in node.getChildNodes():
139 self.visit(child)
140 def visitCallFunc(self, node):
141 def rollup(n):
142 if isinstance(n, compiler.ast.Name):
143 return n.name
144 elif isinstance(n, compiler.ast.Getattr):
145 r = rollup(n.expr)
146 if r:
147 return r + "." + n.attrname
148 name = rollup(node.node)
149 if name:
150 v = Functions.get(name)
151 if v is not None:
152 self.add(node, v, name)
153 self.default(node)
154 def visitClass(self, node):
155 if node.bases:
156 self.add(node, (2,2), "new-style class")
157 if node.decorators:
158 self.add(node, (2,6), "class decorator")
159 self.default(node)
160 def visitDictComp(self, node):
161 self.add(node, (2,7), "dictionary comprehension")
162 self.default(node)
163 def visitFloorDiv(self, node):
164 self.add(node, (2,2), "// operator")
165 self.default(node)
166 def visitFrom(self, node):
167 v = StandardModules.get(node.modname)
168 if v is not None:
169 self.add(node, v, node.modname)
170 for n in node.names:
171 name = node.modname + "." + n[0]
172 v = Functions.get(name)
173 if v is not None:
174 self.add(node, v, name)
175 def visitFunction(self, node):
176 if node.decorators:
177 self.add(node, (2,4), "function decorator")
178 self.default(node)
179 def visitGenExpr(self, node):
180 self.add(node, (2,4), "generator expression")
181 self.default(node)
182 def visitGetattr(self, node):
183 if (isinstance(node.expr, compiler.ast.Const)
184 and isinstance(node.expr.value, str)
185 and node.attrname == "format"):
186 self.add(node, (2,6), "string literal .format()")
187 self.default(node)
188 def visitIfExp(self, node):
189 self.add(node, (2,5), "inline if expression")
190 self.default(node)
191 def visitImport(self, node):
192 for n in node.names:
193 v = StandardModules.get(n[0])
194 if v is not None:
195 self.add(node, v, n[0])
196 self.default(node)
197 def visitName(self, node):
198 v = Identifiers.get(node.name)
199 if v is not None:
200 self.add(node, v, node.name)
201 self.default(node)
202 def visitSet(self, node):
203 self.add(node, (2,7), "set literal")
204 self.default(node)
205 def visitSetComp(self, node):
206 self.add(node, (2,7), "set comprehension")
207 self.default(node)
208 def visitTryFinally(self, node):
209 # try/finally with a suite generates a Stmt node as the body,
210 # but try/except/finally generates a TryExcept as the body
211 if isinstance(node.body, compiler.ast.TryExcept):
212 self.add(node, (2,5), "try/except/finally")
213 self.default(node)
214 def visitWith(self, node):
215 if isinstance(node.body, compiler.ast.With):
216 self.add(node, (2,7), "with statement with multiple contexts")
217 else:
218 self.add(node, (2,5), "with statement")
219 self.default(node)
220 def visitYield(self, node):
221 self.add(node, (2,2), "yield expression")
222 self.default(node)
223
224 def get_versions(source):
225 """Return information about the Python versions required for specific features.
226
227 The return value is a dictionary with keys as a version number as a tuple
228 (for example Python 2.6 is (2,6)) and the value are a list of features that
229 require the indicated Python version.
230 """
231 tree = compiler.parse(source)
232 checker = compiler.walk(tree, NodeChecker())
233 return checker.vers
234
235 def v27(source):
236 if sys.version_info >= (2, 7):
237 return qver(source)
238 else:
239 print >>sys.stderr, "Not all features tested, run --test with Python 2.7"
240 return (2, 7)
241
242 def qver(source):
243 """Return the minimum Python version required to run a particular bit of code.
244
245 >>> qver('print "hello world"')
246 (2, 0)
247 >>> qver('class test(object): pass')
248 (2, 2)
249 >>> qver('yield 1')
250 (2, 2)
251 >>> qver('a // b')
252 (2, 2)
253 >>> qver('True')
254 (2, 2)
255 >>> qver('enumerate(a)')
256 (2, 3)
257 >>> qver('total = sum')
258 (2, 0)
259 >>> qver('sum(a)')
260 (2, 3)
261 >>> qver('(x*x for x in range(5))')
262 (2, 4)
263 >>> qver('class C:\\n @classmethod\\n def m(): pass')
264 (2, 4)
265 >>> qver('y if x else z')
266 (2, 5)
267 >>> qver('import hashlib')
268 (2, 5)
269 >>> qver('from hashlib import md5')
270 (2, 5)
271 >>> qver('import xml.etree.ElementTree')
272 (2, 5)
273 >>> qver('try:\\n try: pass;\\n except: pass;\\nfinally: pass')
274 (2, 0)
275 >>> qver('try: pass;\\nexcept: pass;\\nfinally: pass')
276 (2, 5)
277 >>> qver('from __future__ import with_statement\\nwith x: pass')
278 (2, 5)
279 >>> qver('collections.defaultdict(list)')
280 (2, 5)
281 >>> qver('from collections import defaultdict')
282 (2, 5)
283 >>> qver('"{0}".format(0)')
284 (2, 6)
285 >>> qver('memoryview(x)')
286 (2, 7)
287 >>> v27('{1, 2, 3}')
288 (2, 7)
289 >>> v27('{x for x in s}')
290 (2, 7)
291 >>> v27('{x: y for x in s}')
292 (2, 7)
293 >>> qver('from __future__ import with_statement\\nwith x:\\n with y: pass')
294 (2, 5)
295 >>> v27('from __future__ import with_statement\\nwith x, y: pass')
296 (2, 7)
297 >>> qver('@decorator\\ndef f(): pass')
298 (2, 4)
299 >>> qver('@decorator\\nclass test:\\n pass')
300 (2, 6)
301
302 #>>> qver('0o0')
303 #(2, 6)
304 #>>> qver('@foo\\nclass C: pass')
305 #(2, 6)
306 """
307 return max(get_versions(source).keys())
308
309 Verbose = False
310 MinVersion = (2, 3)
311 Lint = False
312
313 files = []
314 i = 1
315 while i < len(sys.argv):
316 a = sys.argv[i]
317 if a == "--test":
318 import doctest
319 doctest.testmod()
320 sys.exit(0)
321 if a == "-v" or a == "--verbose":
322 Verbose = True
323 elif a == "-l" or a == "--lint":
324 Lint = True
325 elif a == "-m" or a == "--min-version":
326 i += 1
327 MinVersion = tuple(map(int, sys.argv[i].split(".")))
328 else:
329 files.append(a)
330 i += 1
331
332 if not files:
333 print >>sys.stderr, """Usage: %s [options] source ...
334
335 Report minimum Python version required to run given source files.
336
337 -m x.y or --min-version x.y (default 2.3)
338 report version triggers at or above version x.y in verbose mode
339 -v or --verbose
340 print more detailed report of version triggers for each version
341 """ % sys.argv[0]
342 sys.exit(1)
343
344 for fn in files:
345 try:
346 f = open(fn)
347 source = f.read()
348 f.close()
349 ver = get_versions(source)
350 if Verbose:
351 print fn
352 for v in sorted([k for k in ver.keys() if k >= MinVersion], reverse=True):
353 reasons = [x for x in uniq(ver[v]) if x]
354 if reasons:
355 # each reason is (lineno, message)
356 print "\t%s\t%s" % (".".join(map(str, v)), ", ".join([x[1] for x in reasons]))
357 elif Lint:
358 for v in sorted([k for k in ver.keys() if k >= MinVersion], reverse=True):
359 reasons = [x for x in uniq(ver[v]) if x]
360 for r in reasons:
361 # each reason is (lineno, message)
362 print "%s:%s: %s %s" % (fn, r[0], ".".join(map(str, v)), r[1])
363 else:
364 print "%s\t%s" % (".".join(map(str, max(ver.keys()))), fn)
365 except SyntaxError, x:
366 print "%s: syntax error compiling with Python %s: %s" % (fn, platform.python_version(), x)