comparison contrib/import-checker.py @ 28922:4ec62a084e5c

import-checker: parse python code from .t files
author timeless <timeless@mozdev.org>
date Tue, 12 Apr 2016 21:43:56 +0000
parents 02ee31a50002
children 660d8d4ec7aa
comparison
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]