source: find_invalid_code.py @ 1718:8bed138bc404

Last change on this file since 1718:8bed138bc404 was 1718:8bed138bc404, checked in by Stefan Schwarzer <sschwarzer@…>, 4 months ago
Don't inherit from `object` This is no longer needed because we're targeting Python 3 only now.
  • Property exe set to *
File size: 4.8 KB
Line 
1#! /usr/bin/env python
2# Copyright (C) 2008-2018, Stefan Schwarzer <sschwarzer@sschwarzer.net>
3# and ftputil contributors (see `doc/contributors.txt`)
4# See the file LICENSE for licensing terms.
5
6# pylint: disable=redefined-builtin
7
8"""\
9This script scans a directory tree for files which contain code which
10is deprecated or invalid in ftputil %s and above (and even much
11longer). The script uses simple heuristics, so it may miss occurrences
12of deprecated/invalid usage or print some inappropriate lines of your
13files.
14
15Usage: %s start_dir
16
17where 'start_dir' is the starting directory which will be scanned
18recursively for offending code.
19"""
20
21import os
22import re
23import sys
24
25import ftputil.version
26
27
28__doc__ = __doc__ % (ftputil.version.__version__,
29                     os.path.basename(sys.argv[0]))
30
31
32class InvalidFeature:
33    """
34    Store message, regex and locations of a single now invalid
35    feature.
36    """
37
38    # pylint: disable=too-few-public-methods
39    def __init__(self, message, regex):
40        self.message = message
41        if not isinstance(regex, re.compile("").__class__):
42            regex = re.compile(regex)
43        self.regex = regex
44        # Map file name to a list of line numbers (starting at 1).
45        self.locations = {}
46
47
48HOST_REGEX = r"\b(h|host|ftp|ftphost|ftp_host)\b"
49
50invalid_features = [
51  InvalidFeature("Possible use(s) of FTP exceptions via ftputil module",
52                 r"\bftputil\s*?\.\s*?[A-Za-z]+Error\b"),
53  InvalidFeature("Possible use(s) of ftp_error module",
54                 r"\bftp_error\b"),
55  InvalidFeature("Possible use(s) of ftp_stat module",
56                 r"\bftp_stat\b"),
57  InvalidFeature("Possible use(s) of FTPHost.file",
58                 r"{0}\.file\(".format(HOST_REGEX)),
59  InvalidFeature("Possible use(s) of FTPHost.open with text mode",
60                 r"{0}\.open\(.*[\"'](r|w)t?[\"']".format(HOST_REGEX)),
61  InvalidFeature("Possible use(s) of byte string in ignores_line",
62                 r"\bdef ignores_line\("),
63  InvalidFeature("Possible use(s) of byte string in parse_line",
64                 r"\bdef parse_line\("),
65  InvalidFeature("Possible use(s) download with text mode",
66                 r"{0}\.download(_if_newer)?\(".format(HOST_REGEX)),
67  InvalidFeature("Possible use(s) upload with text mode",
68                 r"{0}\.upload(_if_newer)?\(".format(HOST_REGEX)),
69  InvalidFeature("Possible use(s) of xreadline method of FTP file objects",
70                 r"\.\s*?xreadlines\b"),
71]
72
73
74def scan_file(file_name):
75    """
76    Scan a file with name `file_name` for code deprecated in
77    ftputil usage and collect the offending data in the dictionary
78    `features.locations`.
79    """
80    with open(file_name) as fobj:
81        for index, line in enumerate(fobj, start=1):
82            for feature in invalid_features:
83                if feature.regex.search(line):
84                    locations = feature.locations
85                    locations.setdefault(file_name, [])
86                    locations[file_name].append((index, line.rstrip()))
87
88
89def print_results():
90    """
91    Print statistics of deprecated code after the directory has been
92    scanned.
93    """
94    last_message = ""
95    for feature in invalid_features:
96        if feature.message != last_message:
97            print()
98            print(70 * "-")
99            print(feature.message, "...")
100            print()
101            last_message = feature.message
102        locations = feature.locations
103        if not locations:
104            print("   no invalid code found")
105            continue
106        for file_name in sorted(locations.keys()):
107            print(file_name)
108            for line_number, line in locations[file_name]:
109                print("%5d: %s" % (line_number, line))
110    print()
111    print("===========================================")
112    print("Please check your code also by other means!")
113    print("===========================================")
114
115
116def main(start_dir):
117    """
118    Scan a directory tree starting at `start_dir` and print uses
119    of deprecated features, if any were found.
120    """
121    # Deliberately shadow global `start_dir`.
122    # pylint: disable=redefined-outer-name
123    for dir_path, _dirnames, file_names in os.walk(start_dir):
124        for file_name in file_names:
125            abs_name = os.path.abspath(os.path.join(dir_path, file_name))
126            if file_name.endswith(".py"):
127                scan_file(abs_name)
128    print_results()
129
130
131if __name__ == '__main__':
132    if len(sys.argv) == 2:
133        if sys.argv[1] in ("-h", "--help"):
134            print(__doc__)
135            sys.exit(0)
136        start_dir = sys.argv[1]
137        if not os.path.isdir(start_dir):
138            print("Directory %s not found." % start_dir, file=sys.stderr)
139            sys.exit()
140    else:
141        print("Usage: %s start_dir" % sys.argv[0], file=sys.stderr)
142        sys.exit()
143    main(start_dir)
Note: See TracBrowser for help on using the repository browser.