Completed
Push — master ( dcc2a3...3ac76c )
by John
01:14
created

format_app_name()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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