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

pyclean.cli.is_git_available()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
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
    git_available = is_git_available()
43
    description = (
44
        'Remove bytecode files, cache directories, build and test artifacts '
45
        'and other debris in your Python project or elsewhere.'
46
    )
47
    if not git_available:
48
        description += (
49
            '\n\nNote: Install Git to enable the --git-clean feature for '
50
            'removing untracked files.'
51
        )
52
53
    parser = argparse.ArgumentParser(
54
        description=description,
55
        epilog='Made with ♥ by Painless Software, 🄯 Peter Bittner.',
56
    )
57
58
    parser.add_argument('--version', action='version', version=__version__)
59
    parser.add_argument(
60
        'directory',
61
        nargs='+',
62
        help='directory tree to traverse for bytecode and debris',
63
    )
64
    parser.add_argument(
65
        '-i',
66
        '--ignore',
67
        metavar='DIRECTORY',
68
        action='extend',
69
        nargs='+',
70
        default=ignore_default_items,
71
        help='directory that should be ignored (may be specified multiple times;'
72
        ' default: %s)' % ' '.join(ignore_default_items),
73
    )
74
    parser.add_argument(
75
        '-d',
76
        '--debris',
77
        metavar='TOPIC',
78
        action='extend',
79
        nargs='*',
80
        default=argparse.SUPPRESS,
81
        choices=debris_choices,
82
        help='remove leftovers from popular Python development tools'
83
        ' (may be specified multiple times; optional: all %s; default: %s)'
84
        % (
85
            ' '.join(debris_optional_topics),
86
            ' '.join(debris_default_topics),
87
        ),
88
    )
89
    parser.add_argument(
90
        '-e',
91
        '--erase',
92
        metavar='PATTERN',
93
        action='extend',
94
        nargs='+',
95
        default=[],
96
        help='delete files or folders matching a globbing pattern (may be specified'
97
        ' multiple times); this will be interactive unless --yes is used.',
98
    )
99
    parser.add_argument(
100
        '-f',
101
        '--folders',
102
        action='store_true',
103
        help='remove empty directories',
104
    )
105
    parser.add_argument(
106
        '-n',
107
        '--dry-run',
108
        action='store_true',
109
        help='show what would be done',
110
    )
111
112
    verbosity = parser.add_mutually_exclusive_group()
113
    verbosity.add_argument('-q', '--quiet', action='store_true', help='be quiet')
114
    verbosity.add_argument(
115
        '-v',
116
        '--verbose',
117
        action='store_true',
118
        help='be more verbose',
119
    )
120
121
    parser.add_argument(
122
        '-y',
123
        '--yes',
124
        action='store_true',
125
        help='assume yes as answer for interactive questions',
126
    )
127
128
    if git_available:
129
        parser.add_argument(
130
            '-g',
131
            '--git-clean',
132
            action='store_true',
133
            default=False,
134
            help='run git clean to remove untracked files (honors --ignore, --dry-run, and --yes flags)',
135
        )
136
    else:
137
        # Set default when git is not available
138
        parser.set_defaults(git_clean=False)
139
140
    args = parser.parse_args()
141
    init_logging(args)
142
143
    # Check if git-clean is requested but git is not available
144
    if args.git_clean and not git_available:
145
        parser.error('Git is not available. Install Git to use the --git-clean feature.')
146
147
    if args.yes and not args.erase and not args.git_clean:
148
        parser.error('Specifying --yes only makes sense with --erase or --git-clean.')
149
150
    if 'debris' in args:
151
        if 'all' in args.debris:
152
            args.debris = debris_default_topics + debris_optional_topics
153
        elif not args.debris:
154
            args.debris = debris_default_topics
155
        log.debug('Debris topics to scan for: %s', ' '.join(args.debris))
156
    else:
157
        args.debris = []
158
159
    log.debug('Ignored directories: %s', ' '.join(args.ignore))
160
161
    return args
162
163
164
def init_logging(args):
165
    """
166
    Set the log level according to the -v/-q command line options.
167
    """
168
    log_level = (
169
        logging.FATAL if args.quiet else logging.DEBUG if args.verbose else logging.INFO
170
    )
171
    log_format = '%(message)s'
172
    logging.basicConfig(level=log_level, format=log_format)
173
174
175
def main():
176
    """
177
    Entry point for CLI application.
178
    """
179
    args = parse_arguments()
180
181
    try:
182
        modern.pyclean(args)
183
    except Exception as err:
184
        raise SystemExit(err)
185