##// END OF EJS Templates
import-checker: increase portability for python 2.6.x...
FUJIWARA Katsunori -
r29374:7712fcde default
parent child Browse files
Show More
@@ -1,717 +1,717 b''
1 1 #!/usr/bin/env python
2 2
3 3 from __future__ import absolute_import, print_function
4 4
5 5 import ast
6 6 import collections
7 7 import os
8 8 import re
9 9 import sys
10 10
11 11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 12 # to work when run from a virtualenv. The modules were chosen empirically
13 13 # so that the return value matches the return value without virtualenv.
14 14 if True: # disable lexical sorting checks
15 15 import BaseHTTPServer
16 16 import zlib
17 17
18 18 # Whitelist of modules that symbols can be directly imported from.
19 19 allowsymbolimports = (
20 20 '__future__',
21 21 'mercurial.hgweb.common',
22 22 'mercurial.hgweb.request',
23 23 'mercurial.i18n',
24 24 'mercurial.node',
25 25 )
26 26
27 27 # Modules that must be aliased because they are commonly confused with
28 28 # common variables and can create aliasing and readability issues.
29 29 requirealias = {
30 30 'ui': 'uimod',
31 31 }
32 32
33 33 def usingabsolute(root):
34 34 """Whether absolute imports are being used."""
35 35 if sys.version_info[0] >= 3:
36 36 return True
37 37
38 38 for node in ast.walk(root):
39 39 if isinstance(node, ast.ImportFrom):
40 40 if node.module == '__future__':
41 41 for n in node.names:
42 42 if n.name == 'absolute_import':
43 43 return True
44 44
45 45 return False
46 46
47 47 def walklocal(root):
48 48 """Recursively yield all descendant nodes but not in a different scope"""
49 49 todo = collections.deque(ast.iter_child_nodes(root))
50 50 yield root, False
51 51 while todo:
52 52 node = todo.popleft()
53 53 newscope = isinstance(node, ast.FunctionDef)
54 54 if not newscope:
55 55 todo.extend(ast.iter_child_nodes(node))
56 56 yield node, newscope
57 57
58 58 def dotted_name_of_path(path, trimpure=False):
59 59 """Given a relative path to a source file, return its dotted module name.
60 60
61 61 >>> dotted_name_of_path('mercurial/error.py')
62 62 'mercurial.error'
63 63 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
64 64 'mercurial.parsers'
65 65 >>> dotted_name_of_path('zlibmodule.so')
66 66 'zlib'
67 67 """
68 68 parts = path.replace(os.sep, '/').split('/')
69 69 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
70 70 if parts[-1].endswith('module'):
71 71 parts[-1] = parts[-1][:-6]
72 72 if trimpure:
73 73 return '.'.join(p for p in parts if p != 'pure')
74 74 return '.'.join(parts)
75 75
76 76 def fromlocalfunc(modulename, localmods):
77 77 """Get a function to examine which locally defined module the
78 78 target source imports via a specified name.
79 79
80 80 `modulename` is an `dotted_name_of_path()`-ed source file path,
81 81 which may have `.__init__` at the end of it, of the target source.
82 82
83 83 `localmods` is a dict (or set), of which key is an absolute
84 84 `dotted_name_of_path()`-ed source file path of locally defined (=
85 85 Mercurial specific) modules.
86 86
87 87 This function assumes that module names not existing in
88 88 `localmods` are from the Python standard library.
89 89
90 90 This function returns the function, which takes `name` argument,
91 91 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
92 92 matches against locally defined module. Otherwise, it returns
93 93 False.
94 94
95 95 It is assumed that `name` doesn't have `.__init__`.
96 96
97 97 `absname` is an absolute module name of specified `name`
98 98 (e.g. "hgext.convert"). This can be used to compose prefix for sub
99 99 modules or so.
100 100
101 101 `dottedpath` is a `dotted_name_of_path()`-ed source file path
102 102 (e.g. "hgext.convert.__init__") of `name`. This is used to look
103 103 module up in `localmods` again.
104 104
105 105 `hassubmod` is whether it may have sub modules under it (for
106 106 convenient, even though this is also equivalent to "absname !=
107 107 dottednpath")
108 108
109 109 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
110 110 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
111 111 ... 'baz.__init__': True, 'baz.baz1': True }
112 112 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
113 113 >>> # relative
114 114 >>> fromlocal('foo1')
115 115 ('foo.foo1', 'foo.foo1', False)
116 116 >>> fromlocal('bar')
117 117 ('foo.bar', 'foo.bar.__init__', True)
118 118 >>> fromlocal('bar.bar1')
119 119 ('foo.bar.bar1', 'foo.bar.bar1', False)
120 120 >>> # absolute
121 121 >>> fromlocal('baz')
122 122 ('baz', 'baz.__init__', True)
123 123 >>> fromlocal('baz.baz1')
124 124 ('baz.baz1', 'baz.baz1', False)
125 125 >>> # unknown = maybe standard library
126 126 >>> fromlocal('os')
127 127 False
128 128 >>> fromlocal(None, 1)
129 129 ('foo', 'foo.__init__', True)
130 130 >>> fromlocal('foo1', 1)
131 131 ('foo.foo1', 'foo.foo1', False)
132 132 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
133 133 >>> fromlocal2(None, 2)
134 134 ('foo', 'foo.__init__', True)
135 135 >>> fromlocal2('bar2', 1)
136 136 False
137 137 >>> fromlocal2('bar', 2)
138 138 ('foo.bar', 'foo.bar.__init__', True)
139 139 """
140 140 prefix = '.'.join(modulename.split('.')[:-1])
141 141 if prefix:
142 142 prefix += '.'
143 143 def fromlocal(name, level=0):
144 # name is None when relative imports are used.
145 if name is None:
144 # name is false value when relative imports are used.
145 if not name:
146 146 # If relative imports are used, level must not be absolute.
147 147 assert level > 0
148 148 candidates = ['.'.join(modulename.split('.')[:-level])]
149 149 else:
150 150 if not level:
151 151 # Check relative name first.
152 152 candidates = [prefix + name, name]
153 153 else:
154 154 candidates = ['.'.join(modulename.split('.')[:-level]) +
155 155 '.' + name]
156 156
157 157 for n in candidates:
158 158 if n in localmods:
159 159 return (n, n, False)
160 160 dottedpath = n + '.__init__'
161 161 if dottedpath in localmods:
162 162 return (n, dottedpath, True)
163 163 return False
164 164 return fromlocal
165 165
166 166 def list_stdlib_modules():
167 167 """List the modules present in the stdlib.
168 168
169 169 >>> mods = set(list_stdlib_modules())
170 170 >>> 'BaseHTTPServer' in mods
171 171 True
172 172
173 173 os.path isn't really a module, so it's missing:
174 174
175 175 >>> 'os.path' in mods
176 176 False
177 177
178 178 sys requires special treatment, because it's baked into the
179 179 interpreter, but it should still appear:
180 180
181 181 >>> 'sys' in mods
182 182 True
183 183
184 184 >>> 'collections' in mods
185 185 True
186 186
187 187 >>> 'cStringIO' in mods
188 188 True
189 189 """
190 190 for m in sys.builtin_module_names:
191 191 yield m
192 192 # These modules only exist on windows, but we should always
193 193 # consider them stdlib.
194 194 for m in ['msvcrt', '_winreg']:
195 195 yield m
196 196 yield 'builtins' # python3 only
197 197 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
198 198 yield m
199 199 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
200 200 yield m
201 201 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
202 202 # We need to supplement the list of prefixes for the search to work
203 203 # when run from within a virtualenv.
204 204 for mod in (BaseHTTPServer, zlib):
205 205 try:
206 206 # Not all module objects have a __file__ attribute.
207 207 filename = mod.__file__
208 208 except AttributeError:
209 209 continue
210 210 dirname = os.path.dirname(filename)
211 211 for prefix in stdlib_prefixes:
212 212 if dirname.startswith(prefix):
213 213 # Then this directory is redundant.
214 214 break
215 215 else:
216 216 stdlib_prefixes.add(dirname)
217 217 for libpath in sys.path:
218 218 # We want to walk everything in sys.path that starts with
219 219 # something in stdlib_prefixes.
220 220 if not any(libpath.startswith(p) for p in stdlib_prefixes):
221 221 continue
222 222 for top, dirs, files in os.walk(libpath):
223 223 for i, d in reversed(list(enumerate(dirs))):
224 224 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
225 225 or top == libpath and d in ('hgext', 'mercurial')):
226 226 del dirs[i]
227 227 for name in files:
228 228 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
229 229 continue
230 230 if name.startswith('__init__.py'):
231 231 full_path = top
232 232 else:
233 233 full_path = os.path.join(top, name)
234 234 rel_path = full_path[len(libpath) + 1:]
235 235 mod = dotted_name_of_path(rel_path)
236 236 yield mod
237 237
238 238 stdlib_modules = set(list_stdlib_modules())
239 239
240 240 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
241 241 """Given the source of a file as a string, yield the names
242 242 imported by that file.
243 243
244 244 Args:
245 245 source: The python source to examine as a string.
246 246 modulename: of specified python source (may have `__init__`)
247 247 localmods: dict of locally defined module names (may have `__init__`)
248 248 ignore_nested: If true, import statements that do not start in
249 249 column zero will be ignored.
250 250
251 251 Returns:
252 252 A list of absolute module names imported by the given source.
253 253
254 254 >>> f = 'foo/xxx.py'
255 255 >>> modulename = 'foo.xxx'
256 256 >>> localmods = {'foo.__init__': True,
257 257 ... 'foo.foo1': True, 'foo.foo2': True,
258 258 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
259 259 ... 'baz.__init__': True, 'baz.baz1': True }
260 260 >>> # standard library (= not locally defined ones)
261 261 >>> sorted(imported_modules(
262 262 ... 'from stdlib1 import foo, bar; import stdlib2',
263 263 ... modulename, f, localmods))
264 264 []
265 265 >>> # relative importing
266 266 >>> sorted(imported_modules(
267 267 ... 'import foo1; from bar import bar1',
268 268 ... modulename, f, localmods))
269 269 ['foo.bar.bar1', 'foo.foo1']
270 270 >>> sorted(imported_modules(
271 271 ... 'from bar.bar1 import name1, name2, name3',
272 272 ... modulename, f, localmods))
273 273 ['foo.bar.bar1']
274 274 >>> # absolute importing
275 275 >>> sorted(imported_modules(
276 276 ... 'from baz import baz1, name1',
277 277 ... modulename, f, localmods))
278 278 ['baz.__init__', 'baz.baz1']
279 279 >>> # mixed importing, even though it shouldn't be recommended
280 280 >>> sorted(imported_modules(
281 281 ... 'import stdlib, foo1, baz',
282 282 ... modulename, f, localmods))
283 283 ['baz.__init__', 'foo.foo1']
284 284 >>> # ignore_nested
285 285 >>> sorted(imported_modules(
286 286 ... '''import foo
287 287 ... def wat():
288 288 ... import bar
289 289 ... ''', modulename, f, localmods))
290 290 ['foo.__init__', 'foo.bar.__init__']
291 291 >>> sorted(imported_modules(
292 292 ... '''import foo
293 293 ... def wat():
294 294 ... import bar
295 295 ... ''', modulename, f, localmods, ignore_nested=True))
296 296 ['foo.__init__']
297 297 """
298 298 fromlocal = fromlocalfunc(modulename, localmods)
299 299 for node in ast.walk(ast.parse(source, f)):
300 300 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
301 301 continue
302 302 if isinstance(node, ast.Import):
303 303 for n in node.names:
304 304 found = fromlocal(n.name)
305 305 if not found:
306 306 # this should import standard library
307 307 continue
308 308 yield found[1]
309 309 elif isinstance(node, ast.ImportFrom):
310 310 found = fromlocal(node.module, node.level)
311 311 if not found:
312 312 # this should import standard library
313 313 continue
314 314
315 315 absname, dottedpath, hassubmod = found
316 316 if not hassubmod:
317 317 # "dottedpath" is not a package; must be imported
318 318 yield dottedpath
319 319 # examination of "node.names" should be redundant
320 320 # e.g.: from mercurial.node import nullid, nullrev
321 321 continue
322 322
323 323 modnotfound = False
324 324 prefix = absname + '.'
325 325 for n in node.names:
326 326 found = fromlocal(prefix + n.name)
327 327 if not found:
328 328 # this should be a function or a property of "node.module"
329 329 modnotfound = True
330 330 continue
331 331 yield found[1]
332 332 if modnotfound:
333 333 # "dottedpath" is a package, but imported because of non-module
334 334 # lookup
335 335 yield dottedpath
336 336
337 337 def verify_import_convention(module, source, localmods):
338 338 """Verify imports match our established coding convention.
339 339
340 340 We have 2 conventions: legacy and modern. The modern convention is in
341 341 effect when using absolute imports.
342 342
343 343 The legacy convention only looks for mixed imports. The modern convention
344 344 is much more thorough.
345 345 """
346 346 root = ast.parse(source)
347 347 absolute = usingabsolute(root)
348 348
349 349 if absolute:
350 350 return verify_modern_convention(module, root, localmods)
351 351 else:
352 352 return verify_stdlib_on_own_line(root)
353 353
354 354 def verify_modern_convention(module, root, localmods, root_col_offset=0):
355 355 """Verify a file conforms to the modern import convention rules.
356 356
357 357 The rules of the modern convention are:
358 358
359 359 * Ordering is stdlib followed by local imports. Each group is lexically
360 360 sorted.
361 361 * Importing multiple modules via "import X, Y" is not allowed: use
362 362 separate import statements.
363 363 * Importing multiple modules via "from X import ..." is allowed if using
364 364 parenthesis and one entry per line.
365 365 * Only 1 relative import statement per import level ("from .", "from ..")
366 366 is allowed.
367 367 * Relative imports from higher levels must occur before lower levels. e.g.
368 368 "from .." must be before "from .".
369 369 * Imports from peer packages should use relative import (e.g. do not
370 370 "import mercurial.foo" from a "mercurial.*" module).
371 371 * Symbols can only be imported from specific modules (see
372 372 `allowsymbolimports`). For other modules, first import the module then
373 373 assign the symbol to a module-level variable. In addition, these imports
374 374 must be performed before other local imports. This rule only
375 375 applies to import statements outside of any blocks.
376 376 * Relative imports from the standard library are not allowed.
377 377 * Certain modules must be aliased to alternate names to avoid aliasing
378 378 and readability problems. See `requirealias`.
379 379 """
380 380 topmodule = module.split('.')[0]
381 381 fromlocal = fromlocalfunc(module, localmods)
382 382
383 383 # Whether a local/non-stdlib import has been performed.
384 384 seenlocal = None
385 385 # Whether a local/non-stdlib, non-symbol import has been seen.
386 386 seennonsymbollocal = False
387 387 # The last name to be imported (for sorting).
388 388 lastname = None
389 389 # Relative import levels encountered so far.
390 390 seenlevels = set()
391 391
392 392 for node, newscope in walklocal(root):
393 393 def msg(fmt, *args):
394 394 return (fmt % args, node.lineno)
395 395 if newscope:
396 396 # Check for local imports in function
397 397 for r in verify_modern_convention(module, node, localmods,
398 398 node.col_offset + 4):
399 399 yield r
400 400 elif isinstance(node, ast.Import):
401 401 # Disallow "import foo, bar" and require separate imports
402 402 # for each module.
403 403 if len(node.names) > 1:
404 404 yield msg('multiple imported names: %s',
405 405 ', '.join(n.name for n in node.names))
406 406
407 407 name = node.names[0].name
408 408 asname = node.names[0].asname
409 409
410 410 # Ignore sorting rules on imports inside blocks.
411 411 if node.col_offset == root_col_offset:
412 412 if lastname and name < lastname:
413 413 yield msg('imports not lexically sorted: %s < %s',
414 414 name, lastname)
415 415
416 416 lastname = name
417 417
418 418 # stdlib imports should be before local imports.
419 419 stdlib = name in stdlib_modules
420 420 if stdlib and seenlocal and node.col_offset == root_col_offset:
421 421 yield msg('stdlib import "%s" follows local import: %s',
422 422 name, seenlocal)
423 423
424 424 if not stdlib:
425 425 seenlocal = name
426 426
427 427 # Import of sibling modules should use relative imports.
428 428 topname = name.split('.')[0]
429 429 if topname == topmodule:
430 430 yield msg('import should be relative: %s', name)
431 431
432 432 if name in requirealias and asname != requirealias[name]:
433 433 yield msg('%s module must be "as" aliased to %s',
434 434 name, requirealias[name])
435 435
436 436 elif isinstance(node, ast.ImportFrom):
437 437 # Resolve the full imported module name.
438 438 if node.level > 0:
439 439 fullname = '.'.join(module.split('.')[:-node.level])
440 440 if node.module:
441 441 fullname += '.%s' % node.module
442 442 else:
443 443 assert node.module
444 444 fullname = node.module
445 445
446 446 topname = fullname.split('.')[0]
447 447 if topname == topmodule:
448 448 yield msg('import should be relative: %s', fullname)
449 449
450 450 # __future__ is special since it needs to come first and use
451 451 # symbol import.
452 452 if fullname != '__future__':
453 453 if not fullname or fullname in stdlib_modules:
454 454 yield msg('relative import of stdlib module')
455 455 else:
456 456 seenlocal = fullname
457 457
458 458 # Direct symbol import is only allowed from certain modules and
459 459 # must occur before non-symbol imports.
460 460 found = fromlocal(node.module, node.level)
461 461 if found and found[2]: # node.module is a package
462 462 prefix = found[0] + '.'
463 463 symbols = [n.name for n in node.names
464 464 if not fromlocal(prefix + n.name)]
465 465 else:
466 466 symbols = [n.name for n in node.names]
467 467 if node.module and node.col_offset == root_col_offset:
468 468 if symbols and fullname not in allowsymbolimports:
469 469 yield msg('direct symbol import %s from %s',
470 470 ', '.join(symbols), fullname)
471 471
472 472 if symbols and seennonsymbollocal:
473 473 yield msg('symbol import follows non-symbol import: %s',
474 474 fullname)
475 475 if not symbols and fullname not in stdlib_modules:
476 476 seennonsymbollocal = True
477 477
478 478 if not node.module:
479 479 assert node.level
480 480
481 481 # Only allow 1 group per level.
482 482 if (node.level in seenlevels
483 483 and node.col_offset == root_col_offset):
484 484 yield msg('multiple "from %s import" statements',
485 485 '.' * node.level)
486 486
487 487 # Higher-level groups come before lower-level groups.
488 488 if any(node.level > l for l in seenlevels):
489 489 yield msg('higher-level import should come first: %s',
490 490 fullname)
491 491
492 492 seenlevels.add(node.level)
493 493
494 494 # Entries in "from .X import ( ... )" lists must be lexically
495 495 # sorted.
496 496 lastentryname = None
497 497
498 498 for n in node.names:
499 499 if lastentryname and n.name < lastentryname:
500 500 yield msg('imports from %s not lexically sorted: %s < %s',
501 501 fullname, n.name, lastentryname)
502 502
503 503 lastentryname = n.name
504 504
505 505 if n.name in requirealias and n.asname != requirealias[n.name]:
506 506 yield msg('%s from %s must be "as" aliased to %s',
507 507 n.name, fullname, requirealias[n.name])
508 508
509 509 def verify_stdlib_on_own_line(root):
510 510 """Given some python source, verify that stdlib imports are done
511 511 in separate statements from relative local module imports.
512 512
513 513 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
514 514 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
515 515 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
516 516 []
517 517 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
518 518 []
519 519 """
520 520 for node in ast.walk(root):
521 521 if isinstance(node, ast.Import):
522 522 from_stdlib = {False: [], True: []}
523 523 for n in node.names:
524 524 from_stdlib[n.name in stdlib_modules].append(n.name)
525 525 if from_stdlib[True] and from_stdlib[False]:
526 526 yield ('mixed imports\n stdlib: %s\n relative: %s' %
527 527 (', '.join(sorted(from_stdlib[True])),
528 528 ', '.join(sorted(from_stdlib[False]))), node.lineno)
529 529
530 530 class CircularImport(Exception):
531 531 pass
532 532
533 533 def checkmod(mod, imports):
534 534 shortest = {}
535 535 visit = [[mod]]
536 536 while visit:
537 537 path = visit.pop(0)
538 538 for i in sorted(imports.get(path[-1], [])):
539 539 if len(path) < shortest.get(i, 1000):
540 540 shortest[i] = len(path)
541 541 if i in path:
542 542 if i == path[0]:
543 543 raise CircularImport(path)
544 544 continue
545 545 visit.append(path + [i])
546 546
547 547 def rotatecycle(cycle):
548 548 """arrange a cycle so that the lexicographically first module listed first
549 549
550 550 >>> rotatecycle(['foo', 'bar'])
551 551 ['bar', 'foo', 'bar']
552 552 """
553 553 lowest = min(cycle)
554 554 idx = cycle.index(lowest)
555 555 return cycle[idx:] + cycle[:idx] + [lowest]
556 556
557 557 def find_cycles(imports):
558 558 """Find cycles in an already-loaded import graph.
559 559
560 560 All module names recorded in `imports` should be absolute one.
561 561
562 562 >>> from __future__ import print_function
563 563 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
564 564 ... 'top.bar': ['top.baz', 'sys'],
565 565 ... 'top.baz': ['top.foo'],
566 566 ... 'top.qux': ['top.foo']}
567 567 >>> print('\\n'.join(sorted(find_cycles(imports))))
568 568 top.bar -> top.baz -> top.foo -> top.bar
569 569 top.foo -> top.qux -> top.foo
570 570 """
571 571 cycles = set()
572 572 for mod in sorted(imports.keys()):
573 573 try:
574 574 checkmod(mod, imports)
575 575 except CircularImport as e:
576 576 cycle = e.args[0]
577 577 cycles.add(" -> ".join(rotatecycle(cycle)))
578 578 return cycles
579 579
580 580 def _cycle_sortkey(c):
581 581 return len(c), c
582 582
583 583 def embedded(f, modname, src):
584 584 """Extract embedded python code
585 585
586 586 >>> def test(fn, lines):
587 587 ... for s, m, f, l in embedded(fn, "example", lines):
588 588 ... print("%s %s %s" % (m, f, l))
589 589 ... print(repr(s))
590 590 >>> lines = [
591 591 ... 'comment',
592 592 ... ' >>> from __future__ import print_function',
593 593 ... " >>> ' multiline",
594 594 ... " ... string'",
595 595 ... ' ',
596 596 ... 'comment',
597 597 ... ' $ cat > foo.py <<EOF',
598 598 ... ' > from __future__ import print_function',
599 599 ... ' > EOF',
600 600 ... ]
601 601 >>> test("example.t", lines)
602 602 example[2] doctest.py 2
603 603 "from __future__ import print_function\\n' multiline\\nstring'\\n"
604 604 example[7] foo.py 7
605 605 'from __future__ import print_function\\n'
606 606 """
607 607 inlinepython = 0
608 608 shpython = 0
609 609 script = []
610 610 prefix = 6
611 611 t = ''
612 612 n = 0
613 613 for l in src:
614 614 n += 1
615 615 if not l.endswith(b'\n'):
616 616 l += b'\n'
617 617 if l.startswith(b' >>> '): # python inlines
618 618 if shpython:
619 619 print("%s:%d: Parse Error" % (f, n))
620 620 if not inlinepython:
621 621 # We've just entered a Python block.
622 622 inlinepython = n
623 623 t = 'doctest.py'
624 624 script.append(l[prefix:])
625 625 continue
626 626 if l.startswith(b' ... '): # python inlines
627 627 script.append(l[prefix:])
628 628 continue
629 629 cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
630 630 if cat:
631 631 if inlinepython:
632 632 yield ''.join(script), ("%s[%d]" %
633 633 (modname, inlinepython)), t, inlinepython
634 634 script = []
635 635 inlinepython = 0
636 636 shpython = n
637 637 t = cat.group(1)
638 638 continue
639 639 if shpython and l.startswith(b' > '): # sh continuation
640 640 if l == b' > EOF\n':
641 641 yield ''.join(script), ("%s[%d]" %
642 642 (modname, shpython)), t, shpython
643 643 script = []
644 644 shpython = 0
645 645 else:
646 646 script.append(l[4:])
647 647 continue
648 648 if inlinepython and l == b' \n':
649 649 yield ''.join(script), ("%s[%d]" %
650 650 (modname, inlinepython)), t, inlinepython
651 651 script = []
652 652 inlinepython = 0
653 653 continue
654 654
655 655 def sources(f, modname):
656 656 """Yields possibly multiple sources from a filepath
657 657
658 658 input: filepath, modulename
659 659 yields: script(string), modulename, filepath, linenumber
660 660
661 661 For embedded scripts, the modulename and filepath will be different
662 662 from the function arguments. linenumber is an offset relative to
663 663 the input file.
664 664 """
665 665 py = False
666 666 if not f.endswith('.t'):
667 667 with open(f) as src:
668 668 yield src.read(), modname, f, 0
669 669 py = True
670 670 if py or f.endswith('.t'):
671 671 with open(f) as src:
672 672 for script, modname, t, line in embedded(f, modname, src):
673 673 yield script, modname, t, line
674 674
675 675 def main(argv):
676 676 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
677 677 print('Usage: %s {-|file [file] [file] ...}')
678 678 return 1
679 679 if argv[1] == '-':
680 680 argv = argv[:1]
681 681 argv.extend(l.rstrip() for l in sys.stdin.readlines())
682 682 localmods = {}
683 683 used_imports = {}
684 684 any_errors = False
685 685 for source_path in argv[1:]:
686 686 modname = dotted_name_of_path(source_path, trimpure=True)
687 687 localmods[modname] = source_path
688 688 for localmodname, source_path in sorted(localmods.items()):
689 689 for src, modname, name, line in sources(source_path, localmodname):
690 690 try:
691 691 used_imports[modname] = sorted(
692 692 imported_modules(src, modname, name, localmods,
693 693 ignore_nested=True))
694 694 for error, lineno in verify_import_convention(modname, src,
695 695 localmods):
696 696 any_errors = True
697 697 print('%s:%d: %s' % (source_path, lineno + line, error))
698 698 except SyntaxError as e:
699 699 print('%s:%d: SyntaxError: %s' %
700 700 (source_path, e.lineno + line, e))
701 701 cycles = find_cycles(used_imports)
702 702 if cycles:
703 703 firstmods = set()
704 704 for c in sorted(cycles, key=_cycle_sortkey):
705 705 first = c.split()[0]
706 706 # As a rough cut, ignore any cycle that starts with the
707 707 # same module as some other cycle. Otherwise we see lots
708 708 # of cycles that are effectively duplicates.
709 709 if first in firstmods:
710 710 continue
711 711 print('Import cycle:', c)
712 712 firstmods.add(first)
713 713 any_errors = True
714 714 return any_errors != 0
715 715
716 716 if __name__ == '__main__':
717 717 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now