bbarchivist.argutils   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 378
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 148
dl 0
loc 378
rs 8.48
c 0
b 0
f 0
ccs 132
cts 132
cp 1
wmc 49

22 Functions

Rating   Name   Duplication   Size   Complexity  
A default_parser() 0 25 1
A droidlookup_hashtype() 0 11 2
A droidlookup_devicetype() 0 15 4
A valid_carrier() 0 15 4
A generic_windows_shim() 0 24 2
A slim_preamble() 0 8 1
A default_parser_vers() 0 10 2
A valid_method() 0 13 2
A dpf_flags_osr() 0 22 2
A default_parser_flags() 0 14 2
A escreens_pin() 0 16 4
A positive_integer() 0 11 2
A arg_verify_none() 0 12 2
A file_exists() 0 10 2
A escreens_duration() 0 11 2
A longversion() 0 11 3
A dpf_flags_folder() 0 19 2
A shortversion() 0 11 3
A standard_preamble() 0 25 2
A external_version() 0 13 1
A valid_method_poptxz() 0 10 2
A signed_file_args() 0 11 2

How to fix   Complexity   

Complexity

Complex classes like bbarchivist.argutils 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
#!/usr/bin/env python3
2 5
"""This module is used for argument utilities."""
3
4 5
import argparse  # argument parser for filters
5 5
import glob  # file lookup
6 5
import os  # path work
7 5
import subprocess  # running cfp/cap
8 5
import sys  # getattr
9
10 5
from bbarchivist import bbconstants  # constants
11 5
from bbarchivist import utilities  # version checking
12
13 5
__author__ = "Thurask"
14 5
__license__ = "WTFPL v2"
15 5
__copyright__ = "2018-2019 Thurask"
16
17
18 5
def signed_file_args(files):
19
    """
20
    Check if there are between 1 and 6 files supplied to argparse.
21
22
    :param files: List of signed files, between 1 and 6 strings.
23
    :type files: list(str)
24
    """
25 5
    filelist = [file for file in files if file]
26 5
    if not 1 <= len(filelist) <= 6:
27 5
        raise argparse.ArgumentError(argument=None, message="Requires 1-6 signed files")
28 5
    return files
29
30
31 5
def file_exists(file):
32
    """
33
    Check if file exists, raise argparse error if it doesn't.
34
35
    :param file: Path to a file, including extension.
36
    :type file: str
37
    """
38 5
    if not os.path.exists(file):
39 5
        raise argparse.ArgumentError(argument=None, message="{0} not found.".format(file))
40 5
    return file
41
42
43 5
def positive_integer(input_int):
44
    """
45
    Check if number > 0, raise argparse error if it isn't.
46
47
    :param input_int: Integer to check.
48
    :type input_int: str
49
    """
50 5
    if int(input_int) <= 0:
51 5
        info = "{0} is not >=0.".format(str(input_int))
52 5
        raise argparse.ArgumentError(argument=None, message=info)
53 5
    return int(input_int)
54
55
56 5
def valid_method_poptxz(methodlist):
57
    """
58
    Remove .tar.xz support if system is too old.
59
60
    :param methodlist: List of all methods.
61
    :type methodlist: tuple(str)
62
    """
63 5
    if not utilities.new_enough(3, 3):
64 5
        methodlist = [x for x in bbconstants.METHODS if x != "txz"]
65 5
    return methodlist
66
67
68 5
def valid_method(method):
69
    """
70
    Check if compression method is valid, raise argparse error if it isn't.
71
72
    :param method: Compression method to check.
73
    :type method: str
74
    """
75 5
    methodlist = bbconstants.METHODS
76 5
    methodlist = valid_method_poptxz(methodlist)
77 5
    if method not in methodlist:
78 5
        info = "Invalid method {0}.".format(method)
79 5
        raise argparse.ArgumentError(argument=None, message=info)
80 5
    return method
81
82
83 5
def valid_carrier(mcc_mnc):
84
    """
85
    Check if MCC/MNC is valid (1-3 chars), raise argparse error if it isn't.
86
87
    :param mcc_mnc: MCC/MNC to check.
88
    :type mcc_mnc: str
89
    """
90 5
    if not str(mcc_mnc).isdecimal():
91 5
        infod = "Non-integer {0}.".format(str(mcc_mnc))
92 5
        raise argparse.ArgumentError(argument=None, message=infod)
93 5
    if len(str(mcc_mnc)) > 3 or not str(mcc_mnc):
94 5
        infol = "{0} is invalid.".format(str(mcc_mnc))
95 5
        raise argparse.ArgumentError(argument=None, message=infol)
96
    else:
97 5
        return mcc_mnc
98
99
100 5
def escreens_pin(pin):
101
    """
102
    Check if given PIN is valid, raise argparse error if it isn't.
103
104
    :param pin: PIN to check.
105
    :type pin: str
106
    """
107 5
    if len(pin) == 8:
108 5
        try:
109 5
            int(pin, 16)  # hexadecimal-ness
110 5
        except ValueError:
111 5
            raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
112
        else:
113 5
            return pin.lower()
114
    else:
115 5
        raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
116
117
118 5
def escreens_duration(duration):
119
    """
120
    Check if Engineering Screens duration is valid.
121
122
    :param duration: Duration to check.
123
    :type duration: int
124
    """
125 5
    if int(duration) in (1, 3, 6, 15, 30):
126 5
        return int(duration)
127
    else:
128 5
        raise argparse.ArgumentError(argument=None, message="Invalid duration.")
129
130
131 5
def droidlookup_hashtype(method):
132
    """
133
    Check if Android autoloader lookup hash type is valid.
134
135
    :param method: None for regular OS links, "sha256/512" for SHA256 or 512 hash.
136
    :type method: str
137
    """
138 5
    if method.lower() in ("sha512", "sha256"):
139 5
        return method.lower()
140
    else:
141 5
        raise argparse.ArgumentError(argument=None, message="Invalid type.")
142
143
144 5
def droidlookup_devicetype(device):
145
    """
146
    Check if Android autoloader device type is valid.
147
148
    :param device: Android autoloader types to check.
149
    :type device: str
150
    """
151 5
    devices = ("Priv", "DTEK50", "DTEK60", "KEYone", "Aurora", "Motion", "KEY2", "KEY2LE")
152 5
    if device is None:
153 5
        return None
154
    else:
155 5
        for dev in devices:
156 5
            if dev.lower() == device.lower():
157 5
                return dev
158 5
        raise argparse.ArgumentError(argument=None, message="Invalid device.")
159
160
161 5
def shortversion():
162
    """
163
    Get short app version (Git tag).
164
    """
165 5
    if not getattr(sys, 'frozen', False):
166 5
        ver = bbconstants.VERSION
167
    else:
168 5
        verfile = glob.glob(os.path.join(os.getcwd(), "version.txt"))[0]
169 5
        with open(verfile) as afile:
170 5
            ver = afile.read()
171 5
    return ver
172
173
174 5
def longversion():
175
    """
176
    Get long app version (Git tag + commits + hash).
177
    """
178 5
    if not getattr(sys, 'frozen', False):
179 5
        ver = (bbconstants.LONGVERSION, bbconstants.COMMITDATE)
180
    else:
181 5
        verfile = glob.glob(os.path.join(os.getcwd(), "longversion.txt"))[0]
182 5
        with open(verfile) as afile:
183 5
            ver = afile.read().split("\n")
184 5
    return ver
185
186
187 5
def slim_preamble(appname):
188
    """
189
    Standard app name header.
190
191
    :param appname: Name of app.
192
    :type appname: str
193
    """
194 5
    print("~~~{0} VERSION {1}~~~".format(appname.upper(), shortversion()))
195
196
197 5
def standard_preamble(appname, osversion, softwareversion, radioversion, altsw=None):
198
    """
199
    Standard app name, OS, radio and software (plus optional radio software) print block.
200
201
    :param appname: Name of app.
202
    :type appname: str
203
204
    :param osversion: OS version, 10.x.y.zzzz. Required.
205
    :type osversion: str
206
207
    :param radioversion: Radio version, 10.x.y.zzzz. Can be guessed.
208
    :type radioversion: str
209
210
    :param softwareversion: Software release, 10.x.y.zzzz. Can be guessed.
211
    :type softwareversion: str
212
213
    :param altsw: Radio software release, if not the same as OS.
214
    :type altsw: str
215
    """
216 5
    slim_preamble(appname)
217 5
    print("OS VERSION: {0}".format(osversion))
218 5
    print("OS SOFTWARE VERSION: {0}".format(softwareversion))
219 5
    print("RADIO VERSION: {0}".format(radioversion))
220 5
    if altsw is not None:
221 5
        print("RADIO SOFTWARE VERSION: {0}".format(altsw))
222
223
224
225 5
def default_parser_vers(vers=None):
226
    """
227
    Prepare version for default parser.
228
229
    :param vers: Versions: [git commit hash, git commit date]
230
    :param vers: list(str)
231
    """
232 5
    if vers is None:
233 5
        vers = longversion()
234 5
    return vers
235
236
237 5
def default_parser_flags(parser, flags=None):
238
    """
239
    Handle flags for default parser.
240
241
    :param parser: Parser to modify.
242
    :type parser: argparse.ArgumentParser
243
244
    :param flags: Tuple of sections to add.
245
    :type flags: tuple(str)
246
    """
247 5
    if flags is not None:
248 5
        parser = dpf_flags_folder(parser, flags)
249 5
        parser = dpf_flags_osr(parser, flags)
250 5
    return parser
251
252
253 5
def dpf_flags_folder(parser, flags=None):
254
    """
255
    Add generic folder flag to parser.
256
257
    :param parser: Parser to modify.
258
    :type parser: argparse.ArgumentParser
259
260
    :param flags: Tuple of sections to add.
261
    :type flags: tuple(str)
262
    """
263 5
    if "folder" in flags:
264 5
        parser.add_argument("-f",
265
                            "--folder",
266
                            dest="folder",
267
                            help="Working folder",
268
                            default=None,
269
                            metavar="DIR",
270
                            type=file_exists)
271 5
    return parser
272
273
274 5
def dpf_flags_osr(parser, flags=None):
275
    """
276
    Add generic OS/radio/software flags to parser.
277
278
    :param parser: Parser to modify.
279
    :type parser: argparse.ArgumentParser
280
281
    :param flags: Tuple of sections to add.
282
    :type flags: tuple(str)
283
    """
284 5
    if "osr" in flags:
285 5
        parser.add_argument("os",
286
                            help="OS version")
287 5
        parser.add_argument("radio",
288
                            help="Radio version, 10.x.y.zzzz",
289
                            nargs="?",
290
                            default=None)
291 5
        parser.add_argument("swrelease",
292
                            help="Software version, 10.x.y.zzzz",
293
                            nargs="?",
294
                            default=None)
295 5
    return parser
296
297
298 5
def default_parser(name=None, desc=None, flags=None, vers=None):
299
    """
300
    A generic form of argparse's ArgumentParser.
301
302
    :param name: App name.
303
    :type name: str
304
305
    :param desc: App description.
306
    :type desc: str
307
308
    :param flags: Tuple of sections to add.
309
    :type flags: tuple(str)
310
311
    :param vers: Versions: [git commit hash, git commit date]
312
    :param vers: list(str)
313
    """
314 5
    vers = default_parser_vers(vers)
315 5
    homeurl = "https://github.com/thurask/bbarchivist"
316 5
    parser = argparse.ArgumentParser(prog=name, description=desc, epilog=homeurl)
317 5
    parser.add_argument("-v",
318
                        "--version",
319
                        action="version",
320
                        version="{0} {1} committed {2}".format(parser.prog, vers[0], vers[1]))
321 5
    parser = default_parser_flags(parser, flags)
322 5
    return parser
323
324
325 5
def generic_windows_shim(scriptname, scriptdesc, target, version):
326
    """
327
    Generic CFP/CAP runner; Windows only.
328
329
    :param scriptname: Script name, 'bb-something'.
330
    :type scriptname: str
331
332
    :param scriptdesc: Script description, i.e. scriptname -h.
333
    :type scriptdesc: str
334
335
    :param target: Path to file to execute.
336
    :type target: str
337
338
    :param version: Version of target.
339
    :type version: str
340
    """
341 5
    parser = default_parser(scriptname, scriptdesc)
342 5
    capver = "|{0}".format(version)
343 5
    parser = external_version(parser, capver)
344 5
    parser.parse_known_args(sys.argv[1:])
345 5
    if utilities.is_windows():
346 5
        subprocess.call([target] + sys.argv[1:])
347
    else:
348 5
        print("Sorry, Windows only.")
349
350
351 5
def arg_verify_none(argval, message):
352
    """
353
    Check if an argument is None, error out if it is.
354
355
    :param argval: Argument to check.
356
    :type argval: str
357
358
    :param message: Error message to print.
359
    :type message: str
360
    """
361 5
    if argval is None:
362 5
        raise argparse.ArgumentError(argument=None, message=message)
363
364
365 5
def external_version(parser, addition):
366
    """
367
    Modify the version string of argparse.ArgumentParser, adding something.
368
369
    :param parser: Parser to modify.
370
    :type parser: argparse.ArgumentParser
371
372
    :param addition: What to add.
373
    :type addition: str
374
    """
375 5
    verarg = [arg for arg in parser._actions if isinstance(arg, argparse._VersionAction)][0]
376 5
    verarg.version = "{1}{0}".format(addition, verarg.version)
377
    return parser
378