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] |