gitman.cli   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 248
dl 0
loc 333
ccs 120
cts 120
cp 1
rs 10
c 0
b 0
f 0
wmc 22

4 Functions

Rating   Name   Duplication   Size   Complexity  
B _run_command() 0 30 7
B main() 0 216 2
D _get_command() 0 58 12
A _show_error() 0 5 1
1
#!/usr/bin/env python3
2
3 1
"""Command-line interface."""
4
5 1
import argparse
6 1
import sys
7 1
from typing import Dict, List
8
9 1
import log
10 1
11
from . import __version__, commands, common, exceptions
12 1
13
14
def main(args=None, function=None):  # pylint: disable=too-many-statements
15 1
    """Process command-line arguments and run the program."""
16
17
    # Shared options
18
    debug = argparse.ArgumentParser(add_help=False)
19 1
    debug.add_argument(
20 1
        '-V', '--version', action='version', version="GitMan v" + __version__
21 1
    )
22 1
    debug_group = debug.add_mutually_exclusive_group()
23
    debug_group.add_argument(
24 1
        '-v', '--verbose', action='count', default=0, help="enable verbose logging"
25
    )
26 1
    debug_group.add_argument(
27 1
        '-q',
28
        '--quiet',
29 1
        action='store_const',
30 1
        const=-1,
31
        dest='verbose',
32
        help="only display errors and prompts",
33 1
    )
34 1
    project = argparse.ArgumentParser(add_help=False)
35
    project.add_argument(
36 1
        '-r', '--root', metavar='PATH', help="root directory of the project"
37
    )
38 1
    depth = argparse.ArgumentParser(add_help=False)
39
    depth.add_argument(
40
        '-d',
41 1
        '--depth',
42
        type=common.positive_int,
43 1
        default=5,
44
        metavar="NUM",
45
        help="limit the number of dependency levels",
46 1
    )
47 1
    options = argparse.ArgumentParser(add_help=False)
48
    options.add_argument(
49
        '-c',
50
        '--clean',
51 1
        action='store_true',
52 1
        help="delete ignored files in dependencies",
53
    )
54
    options_group = options.add_mutually_exclusive_group()
55 1
    options_group.add_argument(
56
        '-F',
57 1
        '--force',
58
        action='store_true',
59
        help="overwrite uncommitted changes in dependencies",
60
    )
61 1
    options_group.add_argument(
62 1
        '-f',
63
        '--force-interactive',
64
        action='store_true',
65 1
        dest='force_interactive',
66
        help="interactively overwrite uncommitted changes in dependencies",
67 1
    )
68
    options_group.add_argument(
69 1
        '-s',
70 1
        '--skip-changes',
71
        action='store_true',
72
        dest='skip_changes',
73 1
        help="skip dependencies with uncommitted changes",
74
    )
75
76
    # Main parser
77
    parser = argparse.ArgumentParser(
78 1
        prog='gitman',
79 1
        description="A language-agnostic dependency manager using Git.",
80
        parents=[debug],
81 1
        formatter_class=common.WideHelpFormatter,
82
    )
83
    subs = parser.add_subparsers(help="", dest='command', metavar="<command>")
84
85
    # Init parser
86 1
    info = "create a new config file for the project"
87 1
    sub = subs.add_parser(
88
        'init',
89 1
        description=info.capitalize() + '.',
90
        help=info,
91
        parents=[debug],
92
        formatter_class=common.WideHelpFormatter,
93 1
    )
94 1
95
    # Install parser
96 1
    info = "get the specified versions of all dependencies"
97
    sub = subs.add_parser(
98
        'install',
99
        description=info.capitalize() + '.',
100 1
        help=info,
101 1
        parents=[debug, project, depth, options],
102
        formatter_class=common.WideHelpFormatter,
103 1
    )
104
    sub.add_argument('name', nargs='*', help="list of dependencies names to install")
105 1
    sub.add_argument(
106
        '-e', '--fetch', action='store_true', help="always fetch the latest branches"
107 1
    )
108
109
    # Update parser
110
    info = "update dependencies to the latest versions"
111 1
    sub = subs.add_parser(
112 1
        'update',
113
        description=info.capitalize() + '.',
114
        help=info,
115
        parents=[debug, project, depth, options],
116 1
        formatter_class=common.WideHelpFormatter,
117
    )
118
    sub.add_argument('name', nargs='*', help="list of dependencies names to update")
119 1
    sub.add_argument(
120
        '-a',
121
        '--all',
122 1
        action='store_true',
123 1
        dest='recurse',
124 1
        help="also update all nested dependencies",
125
    )
126 1
    sub.add_argument(
127 1
        '-L',
128
        '--skip-lock',
129
        action='store_false',
130 1
        dest='lock',
131 1
        default=None,
132 1
        help="disable recording of updated versions",
133
    )
134 1
135 1
    # List parser
136
    info = "display the current version of each dependency"
137 1
    sub = subs.add_parser(
138 1
        'list',
139 1
        description=info.capitalize() + '.',
140 1
        help=info,
141
        parents=[debug, project, depth],
142
        formatter_class=common.WideHelpFormatter,
143
    )
144 1
    sub.add_argument(
145 1
        '-D',
146 1
        '--fail-if-dirty',
147 1
        action='store_false',
148
        dest='allow_dirty',
149
        help="fail if a source has uncommitted changes",
150 1
    )
151 1
152 1
    # Lock parser
153
    info = "lock the current version of each dependency"
154
    sub = subs.add_parser(
155
        'lock',
156 1
        description=info.capitalize() + '.',
157 1
        help=info,
158 1
        parents=[debug, project],
159 1
        formatter_class=common.WideHelpFormatter,
160
    )
161 1
    sub.add_argument('name', nargs='*', help="list of dependency names to lock")
162 1
163 1
    # Uninstall parser
164
    info = "delete all installed dependencies"
165
    sub = subs.add_parser(
166 1
        'uninstall',
167 1
        description=info.capitalize() + '.',
168 1
        help=info,
169 1
        parents=[debug, project],
170 1
        formatter_class=common.WideHelpFormatter,
171 1
    )
172 1
    sub.add_argument(
173 1
        '-f',
174
        '--force',
175 1
        action='store_true',
176 1
        help="delete uncommitted changes in dependencies",
177 1
    )
178
    sub.add_argument(
179 1
        '-k',
180
        '--keep-location',
181
        dest='keep_location',
182 1
        default=False,
183 1
        action='store_true',
184 1
        help="keep top level folder location",
185 1
    )
186 1
187 1
    # Show parser
188 1
    info = "display the path of a dependency or internal file"
189 1
    sub = subs.add_parser(
190 1
        'show',
191 1
        description=info.capitalize() + '.',
192 1
        help=info,
193 1
        parents=[debug, project],
194 1
        formatter_class=common.WideHelpFormatter,
195 1
    )
196
    sub.add_argument('name', nargs='*', help="display the path of this dependency")
197 1
    sub.add_argument(
198 1
        '-c',
199 1
        '--config',
200
        action='store_true',
201 1
        help="display the path of the config file",
202 1
    )
203
    sub.add_argument(
204 1
        '-l', '--log', action='store_true', help="display the path of the log file"
205 1
    )
206
207
    # Edit parser
208 1
    info = "open the config file in the default editor"
209
    sub = subs.add_parser(
210 1
        'edit',
211 1
        description=info.capitalize() + '.',
212 1
        help=info,
213 1
        parents=[debug, project],
214
        formatter_class=common.WideHelpFormatter,
215
    )
216
217
    # Parse arguments
218
    namespace = parser.parse_args(args=args)
219
220
    # Configure logging
221
    common.configure_logging(namespace.verbose)
222
223
    # Run the program
224
    function, args, kwargs = _get_command(function, namespace)
225
    if function:
226
        _run_command(function, args, kwargs)
227
    else:
228
        parser.print_help()
229
        sys.exit(1)
230
231
232
def _get_command(function, namespace):  # pylint: disable=too-many-statements
233
    args: List = []
234
    kwargs: Dict = {}
235
236
    if namespace.command == 'init':
237
        function = commands.init
238
239
    elif namespace.command in ['install', 'update']:
240
        function = getattr(commands, namespace.command)
241
        args = namespace.name
242
        kwargs.update(
243
            root=namespace.root,
244
            depth=namespace.depth,
245
            force=namespace.force,
246
            force_interactive=namespace.force_interactive,
247
            clean=namespace.clean,
248
            skip_changes=namespace.skip_changes,
249
        )
250
        if namespace.command == 'install':
251
            kwargs.update(fetch=namespace.fetch)
252
        if namespace.command == 'update':
253
            kwargs.update(recurse=namespace.recurse, lock=namespace.lock)
254
255
    elif namespace.command == 'list':
256
        function = commands.display
257
        kwargs.update(
258
            root=namespace.root,
259
            depth=namespace.depth,
260
            allow_dirty=namespace.allow_dirty,
261
        )
262
263
    elif namespace.command == 'lock':
264
        function = getattr(commands, namespace.command)
265
        args = namespace.name
266
        kwargs.update(root=namespace.root)
267
268
    elif namespace.command == 'uninstall':
269
        function = commands.delete
270
        kwargs.update(
271
            root=namespace.root,
272
            force=namespace.force,
273
            keep_location=namespace.keep_location,
274
        )
275
276
    elif namespace.command == 'show':
277
        function = commands.show
278
        args = namespace.name
279
        kwargs.update(root=namespace.root)
280
        if namespace.config:
281
            args.append('__config__')
282
        if namespace.log:
283
            args.append('__log__')
284
285
    elif namespace.command == 'edit':
286
        function = commands.edit
287
        kwargs.update(root=namespace.root)
288
289
    return function, args, kwargs
290
291
292
def _run_command(function, args, kwargs):
293
    success = False
294
    exit_message = None
295
    try:
296
        log.debug("Running %s command...", getattr(function, '__name__', 'a'))
297
        success = function(*args, **kwargs)
298
    except KeyboardInterrupt:
299
        log.debug("Command canceled")
300
    except exceptions.UncommittedChanges as exception:
301
        _show_error(exception)
302
        exit_message = (
303
            "Run again with --force/--force-interactive to discard changes "
304
            "or '--skip-changes' to skip this dependency"
305
        )
306
    except exceptions.ScriptFailure as exception:
307
        _show_error(exception)
308
        exit_message = "Run again with '--force' to ignore script errors"
309
    except exceptions.InvalidConfig as exception:
310
        _show_error(exception)
311
        exit_message = "Adapt config and run again"
312
    finally:
313
        if exit_message:
314
            common.show(exit_message, color='message')
315
            common.newline()
316
317
    if success:
318
        log.debug("Command succeeded")
319
    else:
320
        log.debug("Command failed")
321
        sys.exit(1)
322
323
324
def _show_error(exception):
325
    common.dedent(0)
326
    common.newline()
327
    common.show(str(exception), color='error')
328
    common.newline()
329
330
331
if __name__ == '__main__':  # pragma: no cover (manual test)
332
    main()
333