~sschwarzer/ftputil

ref: c6d0136bf66260c302304a9c53848e08337d405d ftputil/find_invalid_code.py -rwxr-xr-x 4.9 KiB
c6d0136bStefan Schwarzer Mention that `account` and `session_factory` normally aren't needed 6 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#! /usr/bin/env python
# Copyright (C) 2008-2013, Stefan Schwarzer <sschwarzer@sschwarzer.net>
# and ftputil contributors (see `doc/contributors.txt`)
# See the file LICENSE for licensing terms.

# pylint: disable=redefined-builtin

"""\
This script scans a directory tree for files which contain code which
is deprecated or invalid in ftputil %s and above (and even much
longer). The script uses simple heuristics, so it may miss occurrences
of deprecated/invalid usage or print some inappropriate lines of your
files.

Usage: %s start_dir

where 'start_dir' is the starting directory which will be scanned
recursively for offending code.
"""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import os
import re
import sys

import ftputil.version


__doc__ = __doc__ % (ftputil.version.__version__,
                     os.path.basename(sys.argv[0]))


class InvalidFeature(object):
    """
    Store message, regex and locations of a single now invalid
    feature.
    """

    # pylint: disable=too-few-public-methods
    def __init__(self, message, regex):
        self.message = message
        if not isinstance(regex, re.compile("").__class__):
            regex = re.compile(regex)
        self.regex = regex
        # Map file name to a list of line numbers (starting at 1).
        self.locations = {}


HOST_REGEX = r"\b(h|host|ftp|ftphost|ftp_host)\b"

invalid_features = [
  InvalidFeature("Possible use(s) of FTP exceptions via ftputil module",
                 r"\bftputil\s*?\.\s*?[A-Za-z]+Error\b"),
  InvalidFeature("Possible use(s) of ftp_error module",
                 r"\bftp_error\b"),
  InvalidFeature("Possible use(s) of ftp_stat module",
                 r"\bftp_stat\b"),
  InvalidFeature("Possible use(s) of FTPHost.file",
                 r"{0}\.file\(".format(HOST_REGEX)),
  InvalidFeature("Possible use(s) of FTPHost.open with text mode",
                 r"{0}\.open\(.*[\"'](r|w)t?[\"']".format(HOST_REGEX)),
  InvalidFeature("Possible use(s) of byte string in ignores_line",
                 r"\bdef ignores_line\("),
  InvalidFeature("Possible use(s) of byte string in parse_line",
                 r"\bdef parse_line\("),
  InvalidFeature("Possible use(s) download with text mode",
                 r"{0}\.download(_if_newer)?\(".format(HOST_REGEX)),
  InvalidFeature("Possible use(s) upload with text mode",
                 r"{0}\.upload(_if_newer)?\(".format(HOST_REGEX)),
  InvalidFeature("Possible use(s) of xreadline method of FTP file objects",
                 r"\.\s*?xreadlines\b"),
]


def scan_file(file_name):
    """
    Scan a file with name `file_name` for code deprecated in
    ftputil usage and collect the offending data in the dictionary
    `features.locations`.
    """
    with open(file_name) as fobj:
        for index, line in enumerate(fobj, start=1):
            for feature in invalid_features:
                if feature.regex.search(line):
                    locations = feature.locations
                    locations.setdefault(file_name, [])
                    locations[file_name].append((index, line.rstrip()))


def print_results():
    """
    Print statistics of deprecated code after the directory has been
    scanned.
    """
    last_message = ""
    for feature in invalid_features:
        if feature.message != last_message:
            print()
            print(70 * "-")
            print(feature.message, "...")
            print()
            last_message = feature.message
        locations = feature.locations
        if not locations:
            print("   no invalid code found")
            continue
        for file_name in sorted(locations.keys()):
            print(file_name)
            for line_number, line in locations[file_name]:
                print("%5d: %s" % (line_number, line))
    print()
    print("===========================================")
    print("Please check your code also by other means!")
    print("===========================================")


def main(start_dir):
    """
    Scan a directory tree starting at `start_dir` and print uses
    of deprecated features, if any were found.
    """
    # Deliberately shadow global `start_dir`.
    # pylint: disable=redefined-outer-name
    for dir_path, _dirnames, file_names in os.walk(start_dir):
        for file_name in file_names:
            abs_name = os.path.abspath(os.path.join(dir_path, file_name))
            if file_name.endswith(".py"):
                scan_file(abs_name)
    print_results()


if __name__ == '__main__':
    if len(sys.argv) == 2:
        if sys.argv[1] in ("-h", "--help"):
            print(__doc__)
            sys.exit(0)
        start_dir = sys.argv[1]
        if not os.path.isdir(start_dir):
            print("Directory %s not found." % start_dir, file=sys.stderr)
            sys.exit()
    else:
        print("Usage: %s start_dir" % sys.argv[0], file=sys.stderr)
        sys.exit()
    main(start_dir)