Passed
Pull Request — main (#105)
by Peter
01:18
created

pyclean.cli.parse_arguments()   F

Complexity

Conditions 11

Size

Total Lines 136
Code Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 100
nop 0
dl 0
loc 136
rs 3.78
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like pyclean.cli.parse_arguments() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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