Passed
Pull Request — main (#46)
by Peter
01:17
created

pyclean.modern.pyclean()   B

Complexity

Conditions 7

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 13
nop 1
dl 0
loc 17
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
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
    log.info("Total %d files, %d directories %s.",
68
             Runner.unlink_count, Runner.rmdir_count,
69
             "would be removed" if args.dry_run else "removed")
70
71
    if Runner.unlink_failed or Runner.rmdir_failed:
72
        log.debug("%d files, %d directories could not be removed.",
73
                  Runner.unlink_failed, Runner.rmdir_failed)
74
75
76
def descend_and_clean_bytecode(directory):
77
    """
78
    Walk and descend a directory tree, cleaning up bytecode files along
79
    the way. Only delete bytecode folders if they are empty in the end.
80
    """
81
    for child in directory.iterdir():
82
        if child.is_file():
83
            if child.suffix in ['.pyc', '.pyo']:
84
                Runner.unlink(child)
85
        elif child.is_dir():
86
            if child.name in Runner.ignore:
87
                log.debug("Skipping %s", child)
88
            else:
89
                descend_and_clean_bytecode(child)
90
91
            if child.name == '__pycache__':
92
                Runner.rmdir(child)
93
        else:
94
            log.debug("Ignoring %s", child)
95