Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/env python3
      2 
      3 """List all those Python files that require a coding directive
      4 
      5 Usage: findnocoding.py dir1 [dir2...]
      6 """
      7 
      8 __author__ = "Oleg Broytmann, Georg Brandl"
      9 
     10 import sys, os, re, getopt
     11 
     12 # our pysource module finds Python source files
     13 try:
     14     import pysource
     15 except ImportError:
     16     # emulate the module with a simple os.walk
     17     class pysource:
     18         has_python_ext = looks_like_python = can_be_compiled = None
     19         def walk_python_files(self, paths, *args, **kwargs):
     20             for path in paths:
     21                 if os.path.isfile(path):
     22                     yield path.endswith(".py")
     23                 elif os.path.isdir(path):
     24                     for root, dirs, files in os.walk(path):
     25                         for filename in files:
     26                             if filename.endswith(".py"):
     27                                 yield os.path.join(root, filename)
     28     pysource = pysource()
     29 
     30 
     31     print("The pysource module is not available; "
     32                          "no sophisticated Python source file search will be done.", file=sys.stderr)
     33 
     34 
     35 decl_re = re.compile(rb'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)')
     36 blank_re = re.compile(rb'^[ \t\f]*(?:[#\r\n]|$)')
     37 
     38 def get_declaration(line):
     39     match = decl_re.match(line)
     40     if match:
     41         return match.group(1)
     42     return b''
     43 
     44 def has_correct_encoding(text, codec):
     45     try:
     46         str(text, codec)
     47     except UnicodeDecodeError:
     48         return False
     49     else:
     50         return True
     51 
     52 def needs_declaration(fullpath):
     53     try:
     54         infile = open(fullpath, 'rb')
     55     except IOError: # Oops, the file was removed - ignore it
     56         return None
     57 
     58     with infile:
     59         line1 = infile.readline()
     60         line2 = infile.readline()
     61 
     62         if (get_declaration(line1) or
     63             blank_re.match(line1) and get_declaration(line2)):
     64             # the file does have an encoding declaration, so trust it
     65             return False
     66 
     67         # check the whole file for non utf-8 characters
     68         rest = infile.read()
     69 
     70     if has_correct_encoding(line1+line2+rest, "utf-8"):
     71         return False
     72 
     73     return True
     74 
     75 
     76 usage = """Usage: %s [-cd] paths...
     77     -c: recognize Python source files trying to compile them
     78     -d: debug output""" % sys.argv[0]
     79 
     80 if __name__ == '__main__':
     81 
     82     try:
     83         opts, args = getopt.getopt(sys.argv[1:], 'cd')
     84     except getopt.error as msg:
     85         print(msg, file=sys.stderr)
     86         print(usage, file=sys.stderr)
     87         sys.exit(1)
     88 
     89     is_python = pysource.looks_like_python
     90     debug = False
     91 
     92     for o, a in opts:
     93         if o == '-c':
     94             is_python = pysource.can_be_compiled
     95         elif o == '-d':
     96             debug = True
     97 
     98     if not args:
     99         print(usage, file=sys.stderr)
    100         sys.exit(1)
    101 
    102     for fullpath in pysource.walk_python_files(args, is_python):
    103         if debug:
    104             print("Testing for coding: %s" % fullpath)
    105         result = needs_declaration(fullpath)
    106         if result:
    107             print(fullpath)
    108