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

pyclean.modern.remove_debris_for()   A

Complexity

Conditions 2

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nop 1
dl 0
loc 31
rs 9.4
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
log = logging.getLogger(__name__)
13
14
15
class Runner:  # pylint: disable=too-few-public-methods
16
    """Module-level configuration and value store."""
17
    rmdir_count = 0
18
    rmdir_failed = 0
19
    unlink_count = 0
20
    unlink_failed = 0
21
22
23
def remove_file(fileobj):
24
    """Attempt to delete a file object for real."""
25
    log.debug("Deleting file: %s", fileobj)
26
    try:
27
        fileobj.unlink()
28
        Runner.unlink_count += 1
29
    except OSError as err:
30
        log.debug("File not deleted. %s", err)
31
        Runner.unlink_failed += 1
32
33
34
def remove_directory(dirobj):
35
    """Attempt to remove a directory object for real."""
36
    log.debug("Removing directory: %s", dirobj)
37
    try:
38
        dirobj.rmdir()
39
        Runner.rmdir_count += 1
40
    except OSError as err:
41
        log.debug("Directory not removed. %s", err)
42
        Runner.rmdir_failed += 1
43
44
45
def print_filename(fileobj):
46
    """Only display the file name, used with --dry-run."""
47
    log.debug("Would delete file: %s", fileobj)
48
    Runner.unlink_count += 1
49
50
51
def print_dirname(dirobj):
52
    """Only display the directory name, used with --dry-run."""
53
    log.debug("Would delete directory: %s", dirobj)
54
    Runner.rmdir_count += 1
55
56
57
def pyclean(args):
58
    """Cross-platform cleaning of Python bytecode."""
59
    Runner.unlink = print_filename if args.dry_run else remove_file
60
    Runner.rmdir = print_dirname if args.dry_run else remove_directory
61
    Runner.ignore = args.ignore
62
63
    for directory in args.directory:
64
        log.info("Cleaning directory %s", directory)
65
        descend_and_clean_bytecode(Path(directory))
66
67
    for topic in args.debris:
68
        remove_debris_for(topic)
69
70
    log.info("Total %d files, %d directories %s.",
71
             Runner.unlink_count, Runner.rmdir_count,
72
             "would be removed" if args.dry_run else "removed")
73
74
    if Runner.unlink_failed or Runner.rmdir_failed:
75
        log.debug("%d files, %d directories could not be removed.",
76
                  Runner.unlink_failed, Runner.rmdir_failed)
77
78
79
def descend_and_clean_bytecode(directory):
80
    """
81
    Walk and descend a directory tree, cleaning up bytecode files along
82
    the way. Only delete bytecode folders if they are empty in the end.
83
    """
84
    for child in directory.iterdir():
85
        if child.is_file():
86
            if child.suffix in ['.pyc', '.pyo']:
87
                Runner.unlink(child)
88
        elif child.is_dir():
89
            if child.name in Runner.ignore:
90
                log.debug("Skipping %s", child)
91
            else:
92
                descend_and_clean_bytecode(child)
93
94
            if child.name == '__pycache__':
95
                Runner.rmdir(child)
96
        else:
97
            log.debug("Ignoring %s", child)
98
99
100
def remove_debris_for(topic):
101
    """
102
    Clean up debris for a specific topic.
103
    """
104
    topics = {
105
        'build': [
106
            'dist',
107
            'sdist',
108
            '*.egg-info',
109
        ],
110
        'cache': [
111
            '.cache',
112
        ],
113
        'coverage': [
114
            '.coverage',
115
            'coverage.json',
116
            'coverage.xml',
117
            'htmlcov',
118
        ],
119
        'pytest': [
120
            '.pytest_cache',
121
        ],
122
        'tox': [
123
            '.tox',
124
        ],
125
    }
126
127
    log.debug("Cleaning up debris for %s ...", topic)
128
129
    for pathname in topics[topic]:
130
        log.debug(pathname)
131