Completed
Push — master ( 2df547...733f1c )
by John
01:24
created

prep_seven_zip()   A

Complexity

Conditions 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 13
rs 9.4285
c 1
b 0
f 0
1
#!/usr/bin/env python3
2
"""This module is used for miscellaneous utilities."""
3
4
import os  # path work
5
import argparse  # argument parser for filters
6
import platform  # platform info
7
import glob  # cap grabbing
8
import configparser  # config parsing, duh
9
import threading  # get thread for spinner
10
import time  # spinner delay
11
import sys  # streams, version info
12
import itertools  # spinners gonna spin
13
import subprocess  # loader verification
14
from bbarchivist import bbconstants  # cap location, version, filename bits
15
from bbarchivist import compat  # backwards compat
16
from bbarchivist import dummy  # useless stdout
17
18
__author__ = "Thurask"
19
__license__ = "WTFPL v2"
20
__copyright__ = "Copyright 2015-2016 Thurask"
21
22
23
def grab_cap():
24
    """
25
    Figure out where cap is, local, specified or system-supplied.
26
    """
27
    try:
28
        capfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CAP.filename))[0]
29
    except IndexError:
30
        try:
31
            cappath = cappath_config_loader()
32
            capfile = glob.glob(cappath)[0]
33
        except IndexError:
34
            cappath_config_writer(bbconstants.CAP.location)
35
            return bbconstants.CAP.location  # no ini cap
36
        else:
37
            cappath_config_writer(os.path.abspath(capfile))
38
            return os.path.abspath(capfile)  # ini cap
39
    else:
40
        return os.path.abspath(capfile)  # local cap
41
42
43
def grab_cfp():
44
    """
45
    Figure out where cfp is, local or system-supplied.
46
    """
47
    try:
48
        cfpfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CFP.filename))[0]
49
    except IndexError:
50
        cfpfile = bbconstants.CFP.location  # system cfp
51
    return os.path.abspath(cfpfile)  # local cfp
52
53
54
def new_enough(minver):
55
    """
56
    Check if we're at or above a minimum Python version.
57
58
    :param minver: Minimum Python version (3.minver).
59
    :type minver: int
60
    """
61
    return False if minver > sys.version_info[1] else True
62
63
64
def fsizer(file_size):
65
    """
66
    Raw byte file size to human-readable string.
67
68
    :param file_size: Number to parse.
69
    :type file_size: float
70
    """
71
    if file_size is None:
72
        file_size = 0
73
    fsize = float(file_size)
74
    for sfix in ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']:
75
        if fsize < 1024.0:
76
            size = "{0:3.2f}{1}".format(fsize, sfix)
77
            break
78
        else:
79
            fsize /= 1024.0
80
    else:
81
        size = "{0:3.2f}{1}".format(fsize, 'YB')
82
    return size
83
84
85
def signed_file_args(files):
86
    """
87
    Check if there are between 1 and 6 files supplied to argparse.
88
89
    :param files: List of signed files, between 1 and 6 strings.
90
    :type files: list(str)
91
    """
92
    filelist = [file for file in files if file]
93
    if not 1 <= len(filelist) <= 6:
94
        raise argparse.ArgumentError(argument=None, message="Requires 1-6 signed files")
95
    return files
96
97
98
def file_exists(file):
99
    """
100
    Check if file exists, raise argparse error if it doesn't.
101
102
    :param file: Path to a file, including extension.
103
    :type file: str
104
    """
105
    if not os.path.exists(file):
106
        raise argparse.ArgumentError(argument=None, message="{0} not found.".format(file))
107
    return file
108
109
110
def positive_integer(input_int):
111
    """
112
    Check if number > 0, raise argparse error if it isn't.
113
114
    :param input_int: Integer to check.
115
    :type input_int: str
116
    """
117
    if int(input_int) <= 0:
118
        info = "{0} is not >=0.".format(str(input_int))
119
        raise argparse.ArgumentError(argument=None, message=info)
120
    return int(input_int)
121
122
123
def valid_method(method):
124
    """
125
    Check if compression method is valid, raise argparse error if it isn't.
126
127
    :param method: Compression method to check.
128
    :type method: str
129
    """
130
    methodlist = bbconstants.METHODS
131
    if not new_enough(3):
132
        methodlist = [x for x in bbconstants.METHODS if x != "txz"]
133
    if method not in methodlist:
134
        info = "Invalid method {0}.".format(method)
135
        raise argparse.ArgumentError(argument=None, message=info)
136
    return method
137
138
139
def valid_carrier(mcc_mnc):
140
    """
141
    Check if MCC/MNC is valid (1-3 chars), raise argparse error if it isn't.
142
143
    :param mcc_mnc: MCC/MNC to check.
144
    :type mcc_mnc: str
145
    """
146
    if not str(mcc_mnc).isdecimal():
147
        infod = "Non-integer {0}.".format(str(mcc_mnc))
148
        raise argparse.ArgumentError(argument=None, message=infod)
149
    if len(str(mcc_mnc)) > 3 or len(str(mcc_mnc)) == 0:
150
        infol = "{0} is invalid.".format(str(mcc_mnc))
151
        raise argparse.ArgumentError(argument=None, message=infol)
152
    else:
153
        return mcc_mnc
154
155
156
def escreens_pin(pin):
157
    """
158
    Check if given PIN is valid, raise argparse error if it isn't.
159
160
    :param pin: PIN to check.
161
    :type pin: str
162
    """
163
    if len(pin) == 8:
164
        try:
165
            int(pin, 16)  # hexadecimal-ness
166
        except ValueError:
167
            raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
168
        else:
169
            return pin.lower()
170
    else:
171
        raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
172
173
174
def escreens_duration(duration):
175
    """
176
    Check if Engineering Screens duration is valid.
177
178
    :param duration: Duration to check.
179
    :type duration: int
180
    """
181
    if int(duration) in (1, 3, 6, 15, 30):
182
        return int(duration)
183
    else:
184
        raise argparse.ArgumentError(argument=None, message="Invalid duration.")
185
186
187
def droidlookup_hashtype(method):
188
    """
189
    Check if Android autoloader lookup hash type is valid.
190
191
    :param method: None for regular OS links, "sha256/512" for SHA256 or 512 hash.
192
    :type method: str
193
    """
194
    if method.lower() in ("sha512", "sha256"):
195
        return method.lower()
196
    else:
197
        raise argparse.ArgumentError(argument=None, message="Invalid type.")
198
199
200
def droidlookup_devicetype(device):
201
    """
202
    Check if Android autoloader device type is valid.
203
204
    :param device: Android autoloader types to check.
205
    :type device: str
206
    """
207
    devices = ("Priv", "DTEK50")
208
    #devices = ("Priv", "DTEK50", "DTEK60")
209
    if device is None:
210
        return None
211
    else:
212
        for dev in devices:
213
            if dev.lower() == device.lower():
214
                return dev
215
        raise argparse.ArgumentError(argument=None, message="Invalid device.")
216
217
218
def s2b(input_check):
219
    """
220
    Return Boolean interpretation of string input.
221
222
    :param input_check: String to check if it means True or False.
223
    :type input_check: str
224
    """
225
    return str(input_check).lower() in ("yes", "true", "t", "1", "y")
226
227
228
def is_amd64():
229
    """
230
    Check if script is running on an AMD64 system.
231
    """
232
    return platform.machine().endswith("64")
233
234
235
def is_windows():
236
    """
237
    Check if script is running on Windows.
238
    """
239
    return platform.system() == "Windows"
240
241
242
def get_seven_zip(talkative=False):
243
    """
244
    Return name of 7-Zip executable.
245
    On POSIX, it MUST be 7za.
246
    On Windows, it can be installed or supplied with the script.
247
    :func:`win_seven_zip` is used to determine if it's installed.
248
249
    :param talkative: Whether to output to screen. False by default.
250
    :type talkative: bool
251
    """
252
    return win_seven_zip(talkative) if is_windows() else "7za"
253
254
255
def win_seven_zip(talkative=False):
256
    """
257
    For Windows, check where 7-Zip is ("where", pretty much).
258
    Consult registry first for any installed instances of 7-Zip.
259
260
    :param talkative: Whether to output to screen. False by default.
261
    :type talkative: bool
262
    """
263
    if talkative:
264
        print("CHECKING INSTALLED FILES...")
265
    try:
266
        import winreg  # windows registry
267
        hk7z = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\7-Zip")
268
        path = winreg.QueryValueEx(hk7z, "Path")
269
    except OSError as exc:
270
        if talkative:
271
            print("SOMETHING WENT WRONG")
272
            print(str(exc))
273
            print("TRYING LOCAL FILES...")
274
        return win_seven_zip_local(talkative)
275
    else:
276
        if talkative:
277
            print("7ZIP USING INSTALLED FILES")
278
        return '"{0}"'.format(os.path.join(path[0], "7z.exe"))
279
280
281
def win_seven_zip_local(talkative=False):
282
    """
283
    If 7-Zip isn't in the registry, fall back onto supplied executables.
284
    If *those* aren't there, return "error".
285
286
    :param talkative: Whether to output to screen. False by default.
287
    :type talkative: bool
288
    """
289
    listdir = os.listdir(os.getcwd())
290
    filecount = 0
291
    for i in listdir:
292
        if i in ["7za.exe", "7za64.exe"]:
293
            filecount += 1
294
    if filecount == 2:
295
        if talkative:
296
            print("7ZIP USING LOCAL FILES")
297
        if is_amd64():
298
            return "7za64.exe"
299
        else:
300
            return "7za.exe"
301
    else:
302
        if talkative:
303
            print("NO LOCAL FILES")
304
        return "error"
305
306
307
def get_core_count():
308
    """
309
    Find out how many CPU cores this system has.
310
    """
311
    try:
312
        cores = str(compat.enum_cpus())  # 3.4 and up
313
    except NotImplementedError:
314
        cores = "1"  # 3.2-3.3
315
    else:
316
        if compat.enum_cpus() is None:
317
            cores = "1"
318
    return cores
319
320
321
def prep_seven_zip_path(path, talkative=False):
322
    """
323
    Print p7zip path on POSIX, or notify if not there.
324
325
    :param path: Path to use.
326
    :type path: str
327
328
    :param talkative: Whether to output to screen. False by default.
329
    :type talkative: bool
330
    """
331
    if path is None:
332
        if talkative:
333
            print("NO 7ZIP")
334
            print("PLEASE INSTALL p7zip")
335
        return False
336
    else:
337
        if talkative:
338
            print("7ZIP FOUND AT {0}".format(path))
339
        return True
340
341
342
def prep_seven_zip_posix(talkative=False):
343
    """
344
    Check for p7zip on POSIX.
345
346
    :param talkative: Whether to output to screen. False by default.
347
    :type talkative: bool
348
    """
349
    try:
350
        path = compat.where_which("7za")
351
    except ImportError:
352
        if talkative:
353
            print("PLEASE INSTALL SHUTILWHICH WITH PIP")
354
        return False
355
    else:
356
        return prep_seven_zip_path(path, talkative)
357
358
359
def prep_seven_zip(talkative=False):
360
    """
361
    Check for presence of 7-Zip.
362
    On POSIX, check for p7zip.
363
    On Windows, check for 7-Zip.
364
365
    :param talkative: Whether to output to screen. False by default.
366
    :type talkative: bool
367
    """
368
    if is_windows():
369
        return get_seven_zip(talkative) != "error"
370
    else:
371
        return prep_seven_zip_posix(talkative)
372
373
374
def increment(version, inc=3):
375
    """
376
    Increment version by given number. For repeated lookups.
377
378
    :param version: w.x.y.ZZZZ, becomes w.x.y.(ZZZZ + increment).
379
    :type version: str
380
381
    :param inc: What to increment by. Default is 3.
382
    :type inc: str
383
    """
384
    splitos = version.split(".")
385
    splitos[3] = int(splitos[3])
386
    if splitos[3] > 9996:  # prevent overflow
387
        splitos[3] = 0
388
    splitos[3] += int(inc)
389
    splitos[3] = str(splitos[3])
390
    return ".".join(splitos)
391
392
393
def stripper(name):
394
    """
395
    Strip fluff from bar filename.
396
397
    :param name: Bar filename, must contain '-nto+armle-v7+signed.bar'.
398
    :type name: str
399
    """
400
    return name.replace("-nto+armle-v7+signed.bar", "")
401
402
403
def generate_urls(baseurl, osversion, radioversion, core=False):
404
    """
405
    Generate a list of OS URLs and a list of radio URLs based on input.
406
407
    :param baseurl: The URL, from http to the hashed software release.
408
    :type baseurl: str
409
410
    :param osversion: OS version.
411
    :type osversion: str
412
413
    :param radioversion: Radio version.
414
    :type radioversion: str
415
416
    :param core: Whether or not to return core URLs as well.
417
    :type core: bool
418
    """
419
    suffix = "nto+armle-v7+signed.bar"
420
    osurls = [
421
        "{0}/winchester.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
422
        "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
423
        "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
424
        "{0}/qc8974.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
425
    ]
426
    radiourls = [
427
        "{0}/m5730-{1}-{2}".format(baseurl, radioversion, suffix),
428
        "{0}/qc8960-{1}-{2}".format(baseurl, radioversion, suffix),
429
        "{0}/qc8960.omadm-{1}-{2}".format(baseurl, radioversion, suffix),
430
        "{0}/qc8960.wtr-{1}-{2}".format(baseurl, radioversion, suffix),
431
        "{0}/qc8960.wtr5-{1}-{2}".format(baseurl, radioversion, suffix),
432
        "{0}/qc8930.wtr5-{1}-{2}".format(baseurl, radioversion, suffix),
433
        "{0}/qc8974.wtr2-{1}-{2}".format(baseurl, radioversion, suffix)
434
    ]
435
    coreurls = []
436
    splitos = osversion.split(".")
437
    splitos = [int(i) for i in splitos]
438
    if splitos[1] >= 4 or (splitos[1] == 3 and splitos[2] >= 1):  # 10.3.1+
439
        osurls[2] = osurls[2].replace("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30")
440
        osurls[3] = osurls[3].replace("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")
441
    if core:
442
        for url in osurls:
443
            coreurls.append(url.replace(".desktop", ""))
444
    return osurls, radiourls, coreurls
445
446
447
def generate_lazy_urls(baseurl, osversion, radioversion, device):
448
    """
449
    Generate a pair of OS/radio URLs based on input.
450
451
    :param baseurl: The URL, from http to the hashed software release.
452
    :type baseurl: str
453
454
    :param osversion: OS version.
455
    :type osversion: str
456
457
    :param radioversion: Radio version.
458
    :type radioversion: str
459
460
    :param device: Device to use.
461
    :type device: int
462
    """
463
    suffix = "nto+armle-v7+signed.bar"
464
    splitos = [int(i) for i in osversion.split(".")]
465
    if device == 0:
466
        osurl = "{0}/winchester.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
467
        radiourl = "{0}/m5730-{1}-{2}".format(baseurl, radioversion, suffix)
468
    elif device == 1:
469
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
470
        radiourl = "{0}/qc8960-{1}-{2}".format(baseurl, radioversion, suffix)
471
    elif device == 2:
472
        osurl = "{0}/qc8960.verizon_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
473
        radiourl = "{0}/qc8960.omadm-{1}-{2}".format(baseurl, radioversion, suffix)
474
    elif device == 3:
475
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
476
        radiourl = "{0}/qc8960.wtr-{1}-{2}".format(baseurl, radioversion, suffix)
477
    elif device == 4:
478
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
479
        radiourl = "{0}/qc8960.wtr5-{1}-{2}".format(baseurl, radioversion, suffix)
480
    elif device == 5:
481
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
482
        radiourl = "{0}/qc8930.wtr5-{1}-{2}".format(baseurl, radioversion, suffix)
483
        if (splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= 1):
484
            osurl = osurl.replace("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30")
485
    elif device == 6:
486
        osurl = "{0}/qc8974.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
487
        radiourl = "{0}/qc8974.wtr2-{1}-{2}".format(baseurl, radioversion, suffix)
488
        if (splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= 1):
489
            osurl = osurl.replace("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")
490
    return osurl, radiourl
491
492
493
def bulk_urls(baseurl, osversion, radioversion, core=False, alturl=None):
494
    """
495
    Generate all URLs, plus extra Verizon URLs.
496
497
    :param baseurl: The URL, from http to the hashed software release.
498
    :type baseurl: str
499
500
    :param osversion: OS version.
501
    :type osversion: str
502
503
    :param radioversion: Radio version.
504
    :type radioversion: str
505
506
    :param device: Device to use.
507
    :type device: int
508
509
    :param core: Whether or not to return core URLs as well.
510
    :type core: bool
511
512
    :param alturl: The base URL for any alternate radios.
513
    :type alturl: str
514
    """
515
    osurls, radurls, coreurls = generate_urls(baseurl, osversion, radioversion, core)
516
    vzwos, vzwrad = generate_lazy_urls(baseurl, osversion, radioversion, 2)
517
    osurls.append(vzwos)
518
    radurls.append(vzwrad)
519
    vzwcore = vzwos.replace("sfi.desktop", "sfi")
520
    if core:
521
        coreurls.append(vzwcore)
522
    osurls = list(set(osurls))  # pop duplicates
523
    radurls = list(set(radurls))
524
    if core:
525
        coreurls = list(set(coreurls))
526
    if alturl is not None:
527
        radiourls2 = []
528
        for rad in radurls:
529
            radiourls2.append(rad.replace(baseurl, alturl))
530
        radurls = radiourls2
531
        del radiourls2
532
    return osurls, coreurls, radurls
533
534
535
def line_begin():
536
    """
537
    Go to beginning of line, to overwrite whatever's there.
538
    """
539
    sys.stdout.write("\r")
540
    sys.stdout.flush()
541
542
543
def spinner_clear():
544
    """
545
    Get rid of any spinner residue left in stdout.
546
    """
547
    sys.stdout.write("\b \b")
548
    sys.stdout.flush()
549
550
551
class Spinner(object):
552
    """
553
    A basic spinner using itertools. No need for progress.
554
    """
555
556
    def __init__(self):
557
        self.wheel = itertools.cycle(['-', '/', '|', '\\'])
558
        self.file = dummy.UselessStdout()
559
560
    def after(self):
561
        """
562
        Iterate over itertools.cycle, write to file.
563
        """
564
        try:
565
            self.file.write(next(self.wheel))
566
            self.file.flush()
567
            self.file.write("\b\r")
568
            self.file.flush()
569
        except (KeyboardInterrupt, SystemExit):
570
            self.stop()
571
572
    def stop(self):
573
        """
574
        Kill output.
575
        """
576
        self.file = dummy.UselessStdout()
577
578
579
class SpinManager(object):
580
    """
581
    Wraps around the itertools spinner, runs it in another thread.
582
    """
583
584
    def __init__(self):
585
        spinner = Spinner()
586
        self.spinner = spinner
587
        self.thread = threading.Thread(target=self.loop, args=())
588
        self.thread.daemon = True
589
        self.scanning = False
590
        self.spinner.file = dummy.UselessStdout()
591
592
    def start(self):
593
        """
594
        Begin the spinner.
595
        """
596
        self.spinner.file = sys.stderr
597
        self.scanning = True
598
        self.thread.start()
599
600
    def loop(self):
601
        """
602
        Spin if scanning, clean up if not.
603
        """
604
        while self.scanning:
605
            time.sleep(0.5)
606
            try:
607
                line_begin()
608
                self.spinner.after()
609
            except (KeyboardInterrupt, SystemExit):
610
                self.scanning = False
611
                self.stop()
612
613
    def stop(self):
614
        """
615
        Stop the spinner.
616
        """
617
        self.spinner.stop()
618
        self.scanning = False
619
        spinner_clear()
620
        line_begin()
621
        if not is_windows():
622
            print("\n")
623
624
625
def return_and_delete(target):
626
    """
627
    Read text file, then delete it. Return contents.
628
629
    :param target: Text file to read.
630
    :type target: str
631
    """
632
    with open(target, "r") as thefile:
633
        content = thefile.read()
634
    os.remove(target)
635
    return content
636
637
638
def verify_loader_integrity(loaderfile):
639
    """
640
    Test for created loader integrity. Windows-only.
641
642
    :param loaderfile: Path to loader.
643
    :type loaderfile: str
644
    """
645
    if not is_windows():
646
        pass
647
    else:
648
        excode = None
649
        try:
650
            with open(os.devnull, 'rb') as dnull:
651
                cmd = "{0} fileinfo".format(loaderfile)
652
                excode = subprocess.call(cmd, stdout=dnull, stderr=subprocess.STDOUT)
653
        except OSError:
654
            excode = -1
655
        return excode == 0  # 0 if OK, non-zero if something broke
656
657
658
def verify_bulk_loaders(a_dir):
659
    """
660
    Run :func:`verify_loader_integrity` for all files in a dir.
661
662
    :param a_dir: Directory to use.
663
    :type a_dir: str
664
    """
665
    if not is_windows():
666
        pass
667
    else:
668 View Code Duplication
        files = [file for file in os.listdir(a_dir) if not os.path.isdir(file)]
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
669
        brokens = []
670
        for file in files:
671
            if file.endswith(".exe") and file.startswith(bbconstants.PREFIXES):
672
                print("TESTING: {0}".format((file)))
673
                if not verify_loader_integrity(file):
674
                    brokens.append(file)
675
        return brokens
676
677
678
def workers(input_data):
679
    """
680
    Count number of CPU workers, smaller of number of threads and length of data.
681
682
    :param input_data: Input data, some iterable.
683
    :type input_data: list
684
    """
685
    runners = len(input_data) if len(input_data) < compat.enum_cpus() else compat.enum_cpus()
686
    return runners
687
688
689
def prep_logfile():
690
    """
691
    Prepare log file, labeling it with current date. Select folder based on frozen status.
692
    """
693
    logfile = "{0}.txt".format(time.strftime("%Y_%m_%d_%H%M%S"))
694
    root = os.getcwd() if getattr(sys, 'frozen', False) else os.path.expanduser("~")
695
    basefolder = os.path.join(root, "lookuplogs")
696
    os.makedirs(basefolder, exist_ok=True)
697
    record = os.path.join(basefolder, logfile)
698
    open(record, "w").close()
699
    return record
700
701
702
def prepends(file, pre, suf):
703
    """
704
    Check if filename starts with/ends with stuff.
705
706
    :param file: File to check.
707
    :type file: str
708
709
    :param pre: Prefix(es) to check.
710
    :type pre: str or list or tuple
711
712
    :param suf: Suffix(es) to check.
713
    :type suf: str or list or tuple
714
    """
715
    return file.startswith(pre) and file.endswith(suf)
716
717
718
def lprint(iterable):
719
    """
720
    A oneliner for 'for item in x: print item'.
721
722
    :param iterable: Iterable to print.
723
    :type iterable: list/tuple
724
    """
725
    for item in iterable:
726
        print(item)
727
728
729
def cappath_config_loader(homepath=None):
730
    """
731
    Read a ConfigParser file to get cap preferences.
732
733
    :param homepath: Folder containing ini file. Default is user directory.
734
    :type homepath: str
735
    """
736
    config = configparser.ConfigParser()
737
    homepath = os.path.expanduser("~") if homepath is None else homepath
738
    conffile = os.path.join(homepath, "bbarchivist.ini")
739
    if not os.path.exists(conffile):
740
        open(conffile, 'w').close()
741
    config.read(conffile)
742
    if not config.has_section('cap'):
743
        config['cap'] = {}
744
    capini = config['cap']
745
    cappath = capini.get('path', fallback=bbconstants.CAP.location)
746
    return cappath
747
748
749
def cappath_config_writer(cappath=None, homepath=None):
750
    """
751
    Write a ConfigParser file to store cap preferences.
752
753
    :param cappath: Method to use.
754
    :type cappath: str
755
756
    :param homepath: Folder containing ini file. Default is user directory.
757
    :type homepath: str
758
    """
759
    if cappath is None:
760
        cappath = grab_cap()
761
    config = configparser.ConfigParser()
762
    if homepath is None:
763
        homepath = os.path.expanduser("~")
764
    conffile = os.path.join(homepath, "bbarchivist.ini")
765
    if not os.path.exists(conffile):
766
        open(conffile, 'w').close()
767
    config.read(conffile)
768 View Code Duplication
    if not config.has_section('cap'):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
769
        config['cap'] = {}
770
    config['cap']['path'] = cappath
771
    with open(conffile, "w") as configfile:
772
        config.write(configfile)
773