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