contrib/import-checker.py
changeset 28922 4ec62a084e5c
parent 28921 02ee31a50002
child 29122 660d8d4ec7aa
equal deleted inserted replaced
28921:02ee31a50002 28922:4ec62a084e5c
     3 from __future__ import absolute_import, print_function
     3 from __future__ import absolute_import, print_function
     4 
     4 
     5 import ast
     5 import ast
     6 import collections
     6 import collections
     7 import os
     7 import os
       
     8 import re
     8 import sys
     9 import sys
     9 
    10 
    10 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
    11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
    11 # to work when run from a virtualenv.  The modules were chosen empirically
    12 # to work when run from a virtualenv.  The modules were chosen empirically
    12 # so that the return value matches the return value without virtualenv.
    13 # so that the return value matches the return value without virtualenv.
   566     return cycles
   567     return cycles
   567 
   568 
   568 def _cycle_sortkey(c):
   569 def _cycle_sortkey(c):
   569     return len(c), c
   570     return len(c), c
   570 
   571 
       
   572 def embedded(f, modname, src):
       
   573     """Extract embedded python code
       
   574 
       
   575     >>> def test(fn, lines):
       
   576     ...     for s, m, f, l in embedded(fn, "example", lines):
       
   577     ...         print("%s %s %s" % (m, f, l))
       
   578     ...         print(repr(s))
       
   579     >>> lines = [
       
   580     ...   'comment',
       
   581     ...   '  >>> from __future__ import print_function',
       
   582     ...   "  >>> ' multiline",
       
   583     ...   "  ... string'",
       
   584     ...   '  ',
       
   585     ...   'comment',
       
   586     ...   '  $ cat > foo.py <<EOF',
       
   587     ...   '  > from __future__ import print_function',
       
   588     ...   '  > EOF',
       
   589     ... ]
       
   590     >>> test("example.t", lines)
       
   591     example[2] doctest.py 2
       
   592     "from __future__ import print_function\\n' multiline\\nstring'\\n"
       
   593     example[7] foo.py 7
       
   594     'from __future__ import print_function\\n'
       
   595     """
       
   596     inlinepython = 0
       
   597     shpython = 0
       
   598     script = []
       
   599     prefix = 6
       
   600     t = ''
       
   601     n = 0
       
   602     for l in src:
       
   603         n += 1
       
   604         if not l.endswith(b'\n'):
       
   605             l += b'\n'
       
   606         if l.startswith(b'  >>> '): # python inlines
       
   607             if shpython:
       
   608                 print("%s:%d: Parse Error" % (f, n))
       
   609             if not inlinepython:
       
   610                 # We've just entered a Python block.
       
   611                 inlinepython = n
       
   612                 t = 'doctest.py'
       
   613             script.append(l[prefix:])
       
   614             continue
       
   615         if l.startswith(b'  ... '): # python inlines
       
   616             script.append(l[prefix:])
       
   617             continue
       
   618         cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
       
   619         if cat:
       
   620             if inlinepython:
       
   621                 yield ''.join(script), ("%s[%d]" %
       
   622                        (modname, inlinepython)), t, inlinepython
       
   623                 script = []
       
   624                 inlinepython = 0
       
   625             shpython = n
       
   626             t = cat.group(1)
       
   627             continue
       
   628         if shpython and l.startswith(b'  > '): # sh continuation
       
   629             if l == b'  > EOF\n':
       
   630                 yield ''.join(script), ("%s[%d]" %
       
   631                        (modname, shpython)), t, shpython
       
   632                 script = []
       
   633                 shpython = 0
       
   634             else:
       
   635                 script.append(l[4:])
       
   636             continue
       
   637         if inlinepython and l == b'  \n':
       
   638             yield ''.join(script), ("%s[%d]" %
       
   639                    (modname, inlinepython)), t, inlinepython
       
   640             script = []
       
   641             inlinepython = 0
       
   642             continue
       
   643 
   571 def sources(f, modname):
   644 def sources(f, modname):
       
   645     """Yields possibly multiple sources from a filepath
       
   646 
       
   647     input: filepath, modulename
       
   648     yields:  script(string), modulename, filepath, linenumber
       
   649 
       
   650     For embedded scripts, the modulename and filepath will be different
       
   651     from the function arguments. linenumber is an offset relative to
       
   652     the input file.
       
   653     """
       
   654     py = False
   572     if f.endswith('.py'):
   655     if f.endswith('.py'):
   573         with open(f) as src:
   656         with open(f) as src:
   574             yield src.read(), modname
   657             yield src.read(), modname, f, 0
       
   658             py = True
       
   659     if py or f.endswith('.t'):
       
   660         with open(f) as src:
       
   661             for script, modname, t, line in embedded(f, modname, src):
       
   662                 yield script, modname, t, line
   575 
   663 
   576 def main(argv):
   664 def main(argv):
   577     if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
   665     if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
   578         print('Usage: %s {-|file [file] [file] ...}')
   666         print('Usage: %s {-|file [file] [file] ...}')
   579         return 1
   667         return 1
   585     any_errors = False
   673     any_errors = False
   586     for source_path in argv[1:]:
   674     for source_path in argv[1:]:
   587         modname = dotted_name_of_path(source_path, trimpure=True)
   675         modname = dotted_name_of_path(source_path, trimpure=True)
   588         localmods[modname] = source_path
   676         localmods[modname] = source_path
   589     for localmodname, source_path in sorted(localmods.items()):
   677     for localmodname, source_path in sorted(localmods.items()):
   590         for src, modname in sources(source_path, localmodname):
   678         for src, modname, name, line in sources(source_path, localmodname):
   591             try:
   679             try:
   592                 used_imports[modname] = sorted(
   680                 used_imports[modname] = sorted(
   593                     imported_modules(src, modname, source_path, localmods,
   681                     imported_modules(src, modname, name, localmods,
   594                                      ignore_nested=True))
   682                                      ignore_nested=True))
   595                 for error, lineno in verify_import_convention(modname, src,
   683                 for error, lineno in verify_import_convention(modname, src,
   596                                                               localmods):
   684                                                               localmods):
   597                     any_errors = True
   685                     any_errors = True
   598                     print('%s:%d: %s' % (source_path, lineno, error))
   686                     print('%s:%d: %s' % (source_path, lineno + line, error))
   599             except SyntaxError as e:
   687             except SyntaxError as e:
   600                 print('%s:%d: SyntaxError: %s' %
   688                 print('%s:%d: SyntaxError: %s' %
   601                       (source_path, e.lineno, e))
   689                       (source_path, e.lineno + line, e))
   602     cycles = find_cycles(used_imports)
   690     cycles = find_cycles(used_imports)
   603     if cycles:
   691     if cycles:
   604         firstmods = set()
   692         firstmods = set()
   605         for c in sorted(cycles, key=_cycle_sortkey):
   693         for c in sorted(cycles, key=_cycle_sortkey):
   606             first = c.split()[0]
   694             first = c.split()[0]