Passed
Pull Request — main (#50)
by Peter
01:08
created

pyclean.modern.descend_and_clean_bytecode()   B

Complexity

Conditions 7

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nop 1
dl 0
loc 19
rs 8
c 0
b 0
f 0
1
"""
2
Modern, cross-platform, pure-Python pyclean implementation.
3
"""
4
import logging
5
6
try:
7
    from pathlib import Path
8
except ImportError:  # Python 2.7, PyPy2
9
    from warnings import warn
10
    warn("Python 3 required for modern implementation. Python 2 is obsolete.")
11
12
BYTECODE_FILES = ['.pyc', '.pyo']
13
BYTECODE_DIRS = ['__pycache__']
14
DEBRIS_TOPICS = {
15
    'build': [
16
        'dist/',
17
        'sdist/',
18
        '*.egg-info/',
19
    ],
20
    'cache': [
21
        '.cache/',
22
    ],
23
    'coverage': [
24
        '.coverage',
25
        'coverage.json',
26
        'coverage.xml',
27
        'htmlcov/',
28
    ],
29
    'pytest': [
30
        '.pytest_cache/',
31
    ],
32
    'tox': [
33
        '.tox/',
34
    ],
35
}
36
37
log = logging.getLogger(__name__)
38
39
40
class Runner:  # pylint: disable=too-few-public-methods
41
    """Module-level configuration and value store."""
42
    rmdir_count = 0
43
    rmdir_failed = 0
44
    unlink_count = 0
45
    unlink_failed = 0
46
47
48
def remove_file(fileobj):
49
    """Attempt to delete a file object for real."""
50
    log.debug("Deleting file: %s", fileobj)
51
    try:
52
        fileobj.unlink()
53
        Runner.unlink_count += 1
54
    except OSError as err:
55
        log.debug("File not deleted. %s", err)
56
        Runner.unlink_failed += 1
57
58
59
def remove_directory(dirobj):
60
    """Attempt to remove a directory object for real."""
61
    log.debug("Removing directory: %s", dirobj)
62
    try:
63
        dirobj.rmdir()
64
        Runner.rmdir_count += 1
65
    except OSError as err:
66
        log.debug("Directory not removed. %s", err)
67
        Runner.rmdir_failed += 1
68
69
70
def print_filename(fileobj):
71
    """Only display the file name, used with --dry-run."""
72
    log.debug("Would delete file: %s", fileobj)
73
    Runner.unlink_count += 1
74
75
76
def print_dirname(dirobj):
77
    """Only display the directory name, used with --dry-run."""
78
    log.debug("Would delete directory: %s", dirobj)
79
    Runner.rmdir_count += 1
80
81
82
def pyclean(args):
83
    """Cross-platform cleaning of Python bytecode."""
84
    Runner.unlink = print_filename if args.dry_run else remove_file
85
    Runner.rmdir = print_dirname if args.dry_run else remove_directory
86
    Runner.ignore = args.ignore
87
88
    for dir_name in args.directory:
89
        directory = Path(dir_name)
90
91
        log.info("Cleaning directory %s", directory)
92
        descend_and_clean(directory, BYTECODE_FILES, BYTECODE_DIRS)
93
94
    for topic in args.debris:
95
        remove_debris_for(topic, directory)
0 ignored issues
show
introduced by
The variable directory does not seem to be defined in case the for loop on line 88 is not entered. Are you sure this can never be the case?
Loading history...
96
97
    log.info("Total %d files, %d directories %s.",
98
             Runner.unlink_count, Runner.rmdir_count,
99
             "would be removed" if args.dry_run else "removed")
100
101
    if Runner.unlink_failed or Runner.rmdir_failed:
102
        log.debug("%d files, %d directories could not be removed.",
103
                  Runner.unlink_failed, Runner.rmdir_failed)
104
105
106
def descend_and_clean(directory, file_types, dir_names):
107
    """
108
    Walk and descend a directory tree, cleaning up files of a certain type
109
    along the way. Only delete directories if they are empty, in the end.
110
    """
111
    for child in sorted(directory.iterdir()):
112
        if child.is_file():
113
            if child.suffix in file_types:
114
                Runner.unlink(child)
115
        elif child.is_dir():
116
            if child.name in Runner.ignore:
117
                log.debug("Skipping %s", child)
118
            else:
119
                descend_and_clean(child, file_types, dir_names)
120
121
            if child.name in dir_names:
122
                Runner.rmdir(child)
123
        else:
124
            log.debug("Ignoring %s", child)
125
126
127
def remove_debris_for(topic, directory):
128
    """
129
    Clean up debris for a specific topic.
130
    """
131
    log.debug("Scanning for debris of %s ...", topic.title())
132
133
    for path_glob in DEBRIS_TOPICS[topic]:
134
        path_list = sorted(directory.glob(path_glob))
135
136
        for dir_object in path_list:
137
            if dir_object.is_file():
138
                Runner.unlink(dir_object)
139
            elif dir_object.is_dir():
140
                Runner.rmdir(dir_object)
141