#! /usr/bin/env python3
# Copyright (C) 2008-2020, 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
may cause problems in ftputil %s and above. The script uses simple
heuristics, so it may miss occurrences of problematic usage or print
some harmless lines of your files.
Usage: %s start_dir
where 'start_dir' is the starting directory which will be scanned
recursively for potentially problematic code.
"""
import os
import re
import sys
import ftputil.version
__doc__ = __doc__ % (ftputil.version.__version__, os.path.basename(sys.argv[0]))
class InvalidFeature:
"""
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(
"Definition of time shift has changed in ftputil 4.0.0",
r"\.(time_shift\(\)|set_time_shift\()",
),
InvalidFeature(
"Behavior of `FTPHost.makedirs` has changed in ftputil 4.0.0", r"\.makedirs\("
),
]
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 problematic 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)