Passed
Pull Request — main (#105)
by
unknown
01:14
created

pyclean.cli   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 117
dl 0
loc 184
rs 10
c 0
b 0
f 0
wmc 18

4 Functions

Rating   Name   Duplication   Size   Complexity  
F parse_arguments() 0 136 12
A main() 0 10 2
A init_logging() 0 9 3
A is_git_available() 0 5 1
1
# SPDX-FileCopyrightText: 2019 Peter Bittner <[email protected]>
2
#
3
# SPDX-License-Identifier: GPL-3.0-or-later
4
5
"""
6
Command line interface implementation for pyclean.
7
"""
8
9
import argparse
10
import logging
11
import shutil
12
13
from . import __version__, modern
14
15
log = logging.getLogger(__name__)
16
17
18
def is_git_available():
19
    """
20
    Check if git (git-clean) is available in the system.
21
    """
22
    return shutil.which('git') is not None
23
24
25
def parse_arguments():
26
    """
27
    Parse and handle CLI arguments.
28
    """
29
    debris_default_topics = ['cache', 'coverage', 'package', 'pytest', 'ruff']
30
    debris_optional_topics = ['jupyter', 'mypy', 'tox']
31
    debris_choices = ['all', *debris_default_topics, *debris_optional_topics]
32
    ignore_default_items = [
33
        '.git',
34
        '.hg',
35
        '.svn',
36
        '.tox',
37
        '.venv',
38
        'node_modules',
39
        'venv',
40
    ]
41
42
    parser = argparse.ArgumentParser(
43
        description=(
44
            'Remove bytecode files, cache directories, build and test artifacts '
45
            'and other debris in your Python project or elsewhere.'
46
        ),
47
        epilog='Made with ♥ by Painless Software, 🄯 Peter Bittner.',
48
    )
49
50
    parser.add_argument('--version', action='version', version=__version__)
51
    parser.add_argument(
52
        'directory',
53
        nargs='+',
54
        help='directory tree to traverse for bytecode and debris',
55
    )
56
    parser.add_argument(
57
        '-i',
58
        '--ignore',
59
        metavar='DIRECTORY',
60
        action='extend',
61
        nargs='+',
62
        default=ignore_default_items,
63
        help='directory that should be ignored (may be specified multiple times;'
64
        ' default: %s)' % ' '.join(ignore_default_items),
65
    )
66
    parser.add_argument(
67
        '-d',
68
        '--debris',
69
        metavar='TOPIC',
70
        action='extend',
71
        nargs='*',
72
        default=argparse.SUPPRESS,
73
        choices=debris_choices,
74
        help='remove leftovers from popular Python development tools'
75
        ' (may be specified multiple times; optional: all %s; default: %s)'
76
        % (
77
            ' '.join(debris_optional_topics),
78
            ' '.join(debris_default_topics),
79
        ),
80
    )
81
    parser.add_argument(
82
        '-e',
83
        '--erase',
84
        metavar='PATTERN',
85
        action='extend',
86
        nargs='+',
87
        default=[],
88
        help='delete files or folders matching a globbing pattern (may be specified'
89
        ' multiple times); this will be interactive unless --yes is used.',
90
    )
91
    parser.add_argument(
92
        '-f',
93
        '--folders',
94
        action='store_true',
95
        help='remove empty directories',
96
    )
97
    parser.add_argument(
98
        '-n',
99
        '--dry-run',
100
        action='store_true',
101
        help='show what would be done',
102
    )
103
104
    verbosity = parser.add_mutually_exclusive_group()
105
    verbosity.add_argument('-q', '--quiet', action='store_true', help='be quiet')
106
    verbosity.add_argument(
107
        '-v',
108
        '--verbose',
109
        action='store_true',
110
        help='be more verbose',
111
    )
112
113
    parser.add_argument(
114
        '-y',
115
        '--yes',
116
        action='store_true',
117
        help='assume yes as answer for interactive questions',
118
    )
119
120
    git_available = is_git_available()
121
    if git_available:
122
        parser.add_argument(
123
            '-g',
124
            '--git-clean',
125
            action='store_true',
126
            help='run git clean to remove untracked files (honors --ignore, --dry-run, and --yes flags)',
127
        )
128
    else:
129
        # Add a helpful note in the description when git is not available
130
        parser.description += (
131
            '\n\nNote: Install Git to enable the --git-clean feature for '
132
            'removing untracked files.'
133
        )
134
135
    args = parser.parse_args()
136
    init_logging(args)
137
138
    # Check if git-clean is requested but git is not available
139
    if hasattr(args, 'git_clean') and args.git_clean and not git_available:
140
        parser.error('Git is not available. Install Git to use the --git-clean feature.')
141
    
142
    # Set default value for git_clean if not present
143
    if not hasattr(args, 'git_clean'):
144
        args.git_clean = False
145
146
    if args.yes and not args.erase and not args.git_clean:
147
        parser.error('Specifying --yes only makes sense with --erase or --git-clean.')
148
149
    if 'debris' in args:
150
        if 'all' in args.debris:
151
            args.debris = debris_default_topics + debris_optional_topics
152
        elif not args.debris:
153
            args.debris = debris_default_topics
154
        log.debug('Debris topics to scan for: %s', ' '.join(args.debris))
155
    else:
156
        args.debris = []
157
158
    log.debug('Ignored directories: %s', ' '.join(args.ignore))
159
160
    return args
161
162
163
def init_logging(args):
164
    """
165
    Set the log level according to the -v/-q command line options.
166
    """
167
    log_level = (
168
        logging.FATAL if args.quiet else logging.DEBUG if args.verbose else logging.INFO
169
    )
170
    log_format = '%(message)s'
171
    logging.basicConfig(level=log_level, format=log_format)
172
173
174
def main():
175
    """
176
    Entry point for CLI application.
177
    """
178
    args = parse_arguments()
179
180
    try:
181
        modern.pyclean(args)
182
    except Exception as err:
183
        raise SystemExit(err)
184