multiCMD 1.32__py3-none-any.whl → 1.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiCMD.py +213 -147
- {multicmd-1.32.dist-info → multicmd-1.34.dist-info}/METADATA +1 -1
- multicmd-1.34.dist-info/RECORD +6 -0
- multicmd-1.32.dist-info/RECORD +0 -6
- {multicmd-1.32.dist-info → multicmd-1.34.dist-info}/WHEEL +0 -0
- {multicmd-1.32.dist-info → multicmd-1.34.dist-info}/entry_points.txt +0 -0
- {multicmd-1.32.dist-info → multicmd-1.34.dist-info}/top_level.txt +0 -0
multiCMD.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
# "argparse",
|
6
6
|
# ]
|
7
7
|
# ///
|
8
|
+
#%% imports
|
8
9
|
import time
|
9
10
|
import threading
|
10
11
|
import io
|
@@ -18,11 +19,20 @@ import re
|
|
18
19
|
import itertools
|
19
20
|
import signal
|
20
21
|
|
21
|
-
|
22
|
+
#%% global vars
|
23
|
+
version = '1.34'
|
22
24
|
__version__ = version
|
23
|
-
|
24
|
-
__running_threads =
|
25
|
+
COMMIT_DATE = '2025-08-25'
|
26
|
+
__running_threads = set()
|
25
27
|
__variables = {}
|
28
|
+
|
29
|
+
# immutable helpers compiled once at import time
|
30
|
+
_BRACKET_RX = re.compile(r'\[([^\]]+)\]')
|
31
|
+
_ALPHANUM = string.digits + string.ascii_letters
|
32
|
+
_ALPHA_IDX = {c: i for i, c in enumerate(_ALPHANUM)}
|
33
|
+
|
34
|
+
|
35
|
+
#%% objects
|
26
36
|
class Task:
|
27
37
|
def __init__(self, command):
|
28
38
|
self.command = command
|
@@ -41,7 +51,7 @@ class Task:
|
|
41
51
|
if self.thread is not None:
|
42
52
|
return self.thread.is_alive()
|
43
53
|
return False
|
44
|
-
|
54
|
+
|
45
55
|
class AsyncExecutor:
|
46
56
|
def __init__(self, max_threads=1,semaphore=...,timeout=0,quiet=True,dry_run=False,parse=False):
|
47
57
|
'''
|
@@ -227,11 +237,7 @@ class AsyncExecutor:
|
|
227
237
|
'''
|
228
238
|
return [task.returncode for task in self.tasks]
|
229
239
|
|
230
|
-
|
231
|
-
_BRACKET_RX = re.compile(r'\[([^\]]+)\]')
|
232
|
-
_ALPHANUM = string.digits + string.ascii_letters
|
233
|
-
_ALPHA_IDX = {c: i for i, c in enumerate(_ALPHANUM)}
|
234
|
-
|
240
|
+
#%% helper functions
|
235
241
|
def _expand_piece(piece, vars_):
|
236
242
|
"""Turn one comma-separated element from inside [...] into a list of strings."""
|
237
243
|
piece = piece.strip()
|
@@ -287,69 +293,6 @@ def _expand_ranges_fast(inStr):
|
|
287
293
|
# cartesian product of all segments
|
288
294
|
return [''.join(parts) for parts in itertools.product(*segments)]
|
289
295
|
|
290
|
-
def _expand_ranges(inStr):
|
291
|
-
'''
|
292
|
-
Expand ranges in a string
|
293
|
-
|
294
|
-
@params:
|
295
|
-
inStr: The string to expand
|
296
|
-
|
297
|
-
@returns:
|
298
|
-
list[str]: The expanded string
|
299
|
-
'''
|
300
|
-
global __variables
|
301
|
-
expandingStr = [inStr]
|
302
|
-
expandedList = []
|
303
|
-
# all valid alphanumeric characters
|
304
|
-
alphanumeric = string.digits + string.ascii_letters
|
305
|
-
while len(expandingStr) > 0:
|
306
|
-
currentStr = expandingStr.pop()
|
307
|
-
match = re.search(r'\[(.*?)]', currentStr)
|
308
|
-
if not match:
|
309
|
-
expandedList.append(currentStr)
|
310
|
-
continue
|
311
|
-
group = match.group(1)
|
312
|
-
parts = group.split(',')
|
313
|
-
for part in parts:
|
314
|
-
part = part.strip()
|
315
|
-
if ':' in part:
|
316
|
-
variableName, _, part = part.partition(':')
|
317
|
-
__variables[variableName] = part
|
318
|
-
expandingStr.append(currentStr.replace(match.group(0), '', 1))
|
319
|
-
elif '-' in part:
|
320
|
-
try:
|
321
|
-
range_start,_, range_end = part.partition('-')
|
322
|
-
except ValueError:
|
323
|
-
expandedList.append(currentStr)
|
324
|
-
continue
|
325
|
-
range_start = range_start.strip()
|
326
|
-
if range_start in __variables:
|
327
|
-
range_start = __variables[range_start]
|
328
|
-
range_end = range_end.strip()
|
329
|
-
if range_end in __variables:
|
330
|
-
range_end = __variables[range_end]
|
331
|
-
if range_start.isdigit() and range_end.isdigit():
|
332
|
-
padding_length = min(len(range_start), len(range_end))
|
333
|
-
format_str = "{:0" + str(padding_length) + "d}"
|
334
|
-
for i in range(int(range_start), int(range_end) + 1):
|
335
|
-
formatted_i = format_str.format(i)
|
336
|
-
expandingStr.append(currentStr.replace(match.group(0), formatted_i, 1))
|
337
|
-
elif all(c in string.hexdigits for c in range_start + range_end):
|
338
|
-
for i in range(int(range_start, 16), int(range_end, 16) + 1):
|
339
|
-
expandingStr.append(currentStr.replace(match.group(0), format(i, 'x'), 1))
|
340
|
-
else:
|
341
|
-
try:
|
342
|
-
start_index = alphanumeric.index(range_start)
|
343
|
-
end_index = alphanumeric.index(range_end)
|
344
|
-
for i in range(start_index, end_index + 1):
|
345
|
-
expandingStr.append(currentStr.replace(match.group(0), alphanumeric[i], 1))
|
346
|
-
except ValueError:
|
347
|
-
expandedList.append(currentStr)
|
348
|
-
else:
|
349
|
-
expandingStr.append(currentStr.replace(match.group(0), part, 1))
|
350
|
-
expandedList.reverse()
|
351
|
-
return expandedList
|
352
|
-
|
353
296
|
def __handle_stream(stream,target,pre='',post='',quiet=False):
|
354
297
|
'''
|
355
298
|
Handle a stream
|
@@ -519,6 +462,43 @@ def __run_command(task,sem, timeout=60, quiet=False,dry_run=False,with_stdErr=Fa
|
|
519
462
|
else:
|
520
463
|
return task.stdout
|
521
464
|
|
465
|
+
def __format_command(command,expand = False):
|
466
|
+
'''
|
467
|
+
Format a command
|
468
|
+
|
469
|
+
@params:
|
470
|
+
command: The command to format
|
471
|
+
expand: Whether to expand ranges
|
472
|
+
|
473
|
+
@returns:
|
474
|
+
list[list[str]]: The formatted commands sequence
|
475
|
+
'''
|
476
|
+
if isinstance(command,str):
|
477
|
+
if expand:
|
478
|
+
commands = _expand_ranges_fast(command)
|
479
|
+
else:
|
480
|
+
commands = [command]
|
481
|
+
return [command.split() for command in commands]
|
482
|
+
# elif it is a iterable
|
483
|
+
elif hasattr(command,'__iter__'):
|
484
|
+
sanitized_command = []
|
485
|
+
for field in command:
|
486
|
+
if isinstance(field,str):
|
487
|
+
sanitized_command.append(field)
|
488
|
+
else:
|
489
|
+
sanitized_command.append(repr(field))
|
490
|
+
if not expand:
|
491
|
+
return [sanitized_command]
|
492
|
+
sanitized_expanded_command = [_expand_ranges_fast(field) for field in sanitized_command]
|
493
|
+
# now the command had been expanded to list of list of fields with each field expanded to all possible options
|
494
|
+
# we need to generate all possible combinations of the fields
|
495
|
+
# we will use the cartesian product to do this
|
496
|
+
commands = list(itertools.product(*sanitized_expanded_command))
|
497
|
+
return [list(command) for command in commands]
|
498
|
+
else:
|
499
|
+
return __format_command(str(command),expand=expand)
|
500
|
+
|
501
|
+
#%% core funcitons
|
522
502
|
def ping(hosts,timeout=1,max_threads=0,quiet=True,dry_run=False,with_stdErr=False,
|
523
503
|
return_code_only=False,return_object=False,wait_for_return=True,return_true_false=True):
|
524
504
|
'''
|
@@ -560,7 +540,6 @@ def ping(hosts,timeout=1,max_threads=0,quiet=True,dry_run=False,with_stdErr=Fals
|
|
560
540
|
else:
|
561
541
|
return results
|
562
542
|
|
563
|
-
|
564
543
|
def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
|
565
544
|
return_code_only=False,return_object=False,wait_for_return=True, sem = None):
|
566
545
|
'''
|
@@ -585,42 +564,6 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
|
|
585
564
|
dry_run=dry_run, with_stdErr=with_stdErr, return_code_only=return_code_only,
|
586
565
|
return_object=return_object,parse=False,wait_for_return=wait_for_return,sem=sem)[0]
|
587
566
|
|
588
|
-
def __format_command(command,expand = False):
|
589
|
-
'''
|
590
|
-
Format a command
|
591
|
-
|
592
|
-
@params:
|
593
|
-
command: The command to format
|
594
|
-
expand: Whether to expand ranges
|
595
|
-
|
596
|
-
@returns:
|
597
|
-
list[list[str]]: The formatted commands sequence
|
598
|
-
'''
|
599
|
-
if isinstance(command,str):
|
600
|
-
if expand:
|
601
|
-
commands = _expand_ranges_fast(command)
|
602
|
-
else:
|
603
|
-
commands = [command]
|
604
|
-
return [command.split() for command in commands]
|
605
|
-
# elif it is a iterable
|
606
|
-
elif hasattr(command,'__iter__'):
|
607
|
-
sanitized_command = []
|
608
|
-
for field in command:
|
609
|
-
if isinstance(field,str):
|
610
|
-
sanitized_command.append(field)
|
611
|
-
else:
|
612
|
-
sanitized_command.append(repr(field))
|
613
|
-
if not expand:
|
614
|
-
return [sanitized_command]
|
615
|
-
sanitized_expanded_command = [_expand_ranges_fast(field) for field in sanitized_command]
|
616
|
-
# now the command had been expanded to list of list of fields with each field expanded to all possible options
|
617
|
-
# we need to generate all possible combinations of the fields
|
618
|
-
# we will use the cartesian product to do this
|
619
|
-
commands = list(itertools.product(*sanitized_expanded_command))
|
620
|
-
return [list(command) for command in commands]
|
621
|
-
else:
|
622
|
-
return __format_command(str(command),expand=expand)
|
623
|
-
|
624
567
|
def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
|
625
568
|
return_code_only=False,return_object=False, parse = False, wait_for_return = True, sem : threading.Semaphore = None):
|
626
569
|
'''
|
@@ -662,7 +605,7 @@ def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,wit
|
|
662
605
|
for thread in threads:
|
663
606
|
thread.join()
|
664
607
|
else:
|
665
|
-
__running_threads.
|
608
|
+
__running_threads.update(threads)
|
666
609
|
else:
|
667
610
|
# just process the commands sequentially
|
668
611
|
sem = threading.Semaphore(1)
|
@@ -689,9 +632,27 @@ def join_threads(threads=__running_threads,timeout=None):
|
|
689
632
|
@returns:
|
690
633
|
None
|
691
634
|
'''
|
635
|
+
global __running_threads
|
692
636
|
for thread in threads:
|
693
637
|
thread.join(timeout=timeout)
|
638
|
+
if threads is __running_threads:
|
639
|
+
__running_threads = {t for t in threads if t.is_alive()}
|
694
640
|
|
641
|
+
def main():
|
642
|
+
parser = argparse.ArgumentParser(description='Run multiple commands in parallel')
|
643
|
+
parser.add_argument('commands', metavar='command', type=str, nargs='+',help='commands to run')
|
644
|
+
parser.add_argument('-p','--parse', action='store_true',help='Parse ranged input and expand them into multiple commands')
|
645
|
+
parser.add_argument('-t','--timeout', metavar='timeout', type=int, default=60,help='timeout for each command')
|
646
|
+
parser.add_argument('-m','--max_threads', metavar='max_threads', type=int, default=1,help='maximum number of threads to use')
|
647
|
+
parser.add_argument('-q','--quiet', action='store_true',help='quiet mode')
|
648
|
+
parser.add_argument('-V','--version', action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} by pan@zopyr.us')
|
649
|
+
args = parser.parse_args()
|
650
|
+
run_commands(args.commands, args.timeout, args.max_threads, args.quiet,parse = args.parse, with_stdErr=True)
|
651
|
+
|
652
|
+
if __name__ == '__main__':
|
653
|
+
main()
|
654
|
+
|
655
|
+
#%% misc functions
|
695
656
|
def input_with_timeout_and_countdown(timeout, prompt='Please enter your selection'):
|
696
657
|
"""
|
697
658
|
Read an input from the user with a timeout and a countdown.
|
@@ -718,6 +679,148 @@ def input_with_timeout_and_countdown(timeout, prompt='Please enter your selectio
|
|
718
679
|
# If there is no input, return None
|
719
680
|
return None
|
720
681
|
|
682
|
+
def pretty_format_table(data, delimiter = '\t',header = None):
|
683
|
+
version = 1.11
|
684
|
+
_ = version
|
685
|
+
if not data:
|
686
|
+
return ''
|
687
|
+
if isinstance(data, str):
|
688
|
+
data = data.strip('\n').split('\n')
|
689
|
+
data = [line.split(delimiter) for line in data]
|
690
|
+
elif isinstance(data, dict):
|
691
|
+
# flatten the 2D dict to a list of lists
|
692
|
+
if isinstance(next(iter(data.values())), dict):
|
693
|
+
tempData = [['key'] + list(next(iter(data.values())).keys())]
|
694
|
+
tempData.extend( [[key] + list(value.values()) for key, value in data.items()])
|
695
|
+
data = tempData
|
696
|
+
else:
|
697
|
+
# it is a dict of lists
|
698
|
+
data = [[key] + list(value) for key, value in data.items()]
|
699
|
+
elif not isinstance(data, list):
|
700
|
+
data = list(data)
|
701
|
+
# format the list into 2d list of list of strings
|
702
|
+
if isinstance(data[0], dict):
|
703
|
+
tempData = [data[0].keys()]
|
704
|
+
tempData.extend([list(item.values()) for item in data])
|
705
|
+
data = tempData
|
706
|
+
data = [[str(item) for item in row] for row in data]
|
707
|
+
num_cols = len(data[0])
|
708
|
+
col_widths = [0] * num_cols
|
709
|
+
# Calculate the maximum width of each column
|
710
|
+
for c in range(num_cols):
|
711
|
+
#col_widths[c] = max(len(row[c]) for row in data)
|
712
|
+
# handle ansii escape sequences
|
713
|
+
col_widths[c] = max(len(re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]','',row[c])) for row in data)
|
714
|
+
if header:
|
715
|
+
header_widths = [len(re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]', '', col)) for col in header]
|
716
|
+
col_widths = [max(col_widths[i], header_widths[i]) for i in range(num_cols)]
|
717
|
+
# Build the row format string
|
718
|
+
row_format = ' | '.join('{{:<{}}}'.format(width) for width in col_widths)
|
719
|
+
# Print the header
|
720
|
+
if not header:
|
721
|
+
header = data[0]
|
722
|
+
outTable = []
|
723
|
+
outTable.append(row_format.format(*header))
|
724
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
725
|
+
for row in data[1:]:
|
726
|
+
# if the row is empty, print an divider
|
727
|
+
if not any(row):
|
728
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
729
|
+
else:
|
730
|
+
outTable.append(row_format.format(*row))
|
731
|
+
else:
|
732
|
+
# pad / truncate header to appropriate length
|
733
|
+
if isinstance(header,str):
|
734
|
+
header = header.split(delimiter)
|
735
|
+
if len(header) < num_cols:
|
736
|
+
header += ['']*(num_cols-len(header))
|
737
|
+
elif len(header) > num_cols:
|
738
|
+
header = header[:num_cols]
|
739
|
+
outTable = []
|
740
|
+
outTable.append(row_format.format(*header))
|
741
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
742
|
+
for row in data:
|
743
|
+
# if the row is empty, print an divider
|
744
|
+
if not any(row):
|
745
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
746
|
+
else:
|
747
|
+
outTable.append(row_format.format(*row))
|
748
|
+
return '\n'.join(outTable) + '\n'
|
749
|
+
|
750
|
+
def parseTable(data,sort=False):
|
751
|
+
if isinstance(data, str):
|
752
|
+
data = data.strip('\n').split('\n')
|
753
|
+
header_line = data[0]
|
754
|
+
# Use regex to find column names and their positions
|
755
|
+
pattern = r'(\S(?:.*?\S)?)(?=\s{2,}|\s*$)'
|
756
|
+
matches = list(re.finditer(pattern, header_line))
|
757
|
+
data_list = [[]]
|
758
|
+
columns = []
|
759
|
+
for i, match in enumerate(matches):
|
760
|
+
col_name = match.group(1)
|
761
|
+
data_list[0].append(col_name)
|
762
|
+
start = match.start()
|
763
|
+
if i + 1 < len(matches):
|
764
|
+
end = matches[i+1].start()
|
765
|
+
else:
|
766
|
+
end = None # Last column goes till the end
|
767
|
+
columns.append((col_name, start, end))
|
768
|
+
for line in data[1:]:
|
769
|
+
if not line.strip():
|
770
|
+
continue # Skip empty lines
|
771
|
+
row = []
|
772
|
+
for col_name, start, end in columns:
|
773
|
+
if end is not None:
|
774
|
+
value = line[start:end].strip()
|
775
|
+
else:
|
776
|
+
value = line[start:].strip()
|
777
|
+
row.append(value)
|
778
|
+
data_list.append(row)
|
779
|
+
# sort data_list[1:] by the first column
|
780
|
+
if sort:
|
781
|
+
data_list[1:] = sorted(data_list[1:], key=lambda x: x[0])
|
782
|
+
return data_list
|
783
|
+
|
784
|
+
def slugify(value, allow_unicode=False):
|
785
|
+
import unicodedata
|
786
|
+
"""
|
787
|
+
Taken from https://github.com/django/django/blob/master/django/utils/text.py
|
788
|
+
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
|
789
|
+
dashes to single dashes. Remove characters that aren't alphanumerics,
|
790
|
+
underscores, or hyphens. Convert to lowercase. Also strip leading and
|
791
|
+
trailing whitespace, dashes, and underscores.
|
792
|
+
"""
|
793
|
+
value = str(value)
|
794
|
+
if allow_unicode:
|
795
|
+
value = unicodedata.normalize('NFKC', value)
|
796
|
+
else:
|
797
|
+
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
798
|
+
value = re.sub(r'[^\w\s-]', '', value.lower())
|
799
|
+
return re.sub(r'[-\s]+', '-', value).strip('-_')
|
800
|
+
|
801
|
+
def get_terminal_size():
|
802
|
+
'''
|
803
|
+
Get the terminal size
|
804
|
+
|
805
|
+
@params:
|
806
|
+
None
|
807
|
+
|
808
|
+
@returns:
|
809
|
+
(int,int): the number of columns and rows of the terminal
|
810
|
+
'''
|
811
|
+
try:
|
812
|
+
import os
|
813
|
+
_tsize = os.get_terminal_size()
|
814
|
+
except:
|
815
|
+
try:
|
816
|
+
import fcntl, termios, struct
|
817
|
+
packed = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))
|
818
|
+
_tsize = struct.unpack('HHHH', packed)[:2]
|
819
|
+
except:
|
820
|
+
import shutil
|
821
|
+
_tsize = shutil.get_terminal_size(fallback=(120, 30))
|
822
|
+
return _tsize
|
823
|
+
|
721
824
|
def _genrate_progress_bar(iteration, total, prefix='', suffix='',columns=120):
|
722
825
|
'''
|
723
826
|
Generate a progress bar string
|
@@ -777,29 +880,6 @@ def _genrate_progress_bar(iteration, total, prefix='', suffix='',columns=120):
|
|
777
880
|
lineOut += suffix
|
778
881
|
return lineOut
|
779
882
|
|
780
|
-
def get_terminal_size():
|
781
|
-
'''
|
782
|
-
Get the terminal size
|
783
|
-
|
784
|
-
@params:
|
785
|
-
None
|
786
|
-
|
787
|
-
@returns:
|
788
|
-
(int,int): the number of columns and rows of the terminal
|
789
|
-
'''
|
790
|
-
try:
|
791
|
-
import os
|
792
|
-
_tsize = os.get_terminal_size()
|
793
|
-
except:
|
794
|
-
try:
|
795
|
-
import fcntl, termios, struct
|
796
|
-
packed = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))
|
797
|
-
_tsize = struct.unpack('HHHH', packed)[:2]
|
798
|
-
except:
|
799
|
-
import shutil
|
800
|
-
_tsize = shutil.get_terminal_size(fallback=(120, 30))
|
801
|
-
return _tsize
|
802
|
-
|
803
883
|
def print_progress_bar(iteration, total, prefix='', suffix=''):
|
804
884
|
'''
|
805
885
|
Call in a loop to create terminal progress bar
|
@@ -823,17 +903,3 @@ def print_progress_bar(iteration, total, prefix='', suffix=''):
|
|
823
903
|
except:
|
824
904
|
if iteration % 5 == 0:
|
825
905
|
print(_genrate_progress_bar(iteration, total, prefix, suffix))
|
826
|
-
|
827
|
-
def main():
|
828
|
-
parser = argparse.ArgumentParser(description='Run multiple commands in parallel')
|
829
|
-
parser.add_argument('commands', metavar='command', type=str, nargs='+',help='commands to run')
|
830
|
-
parser.add_argument('-p','--parse', action='store_true',help='Parse ranged input and expand them into multiple commands')
|
831
|
-
parser.add_argument('-t','--timeout', metavar='timeout', type=int, default=60,help='timeout for each command')
|
832
|
-
parser.add_argument('-m','--max_threads', metavar='max_threads', type=int, default=1,help='maximum number of threads to use')
|
833
|
-
parser.add_argument('-q','--quiet', action='store_true',help='quiet mode')
|
834
|
-
parser.add_argument('-V','--version', action='version', version=f'%(prog)s {version} by pan@zopyr.us')
|
835
|
-
args = parser.parse_args()
|
836
|
-
run_commands(args.commands, args.timeout, args.max_threads, args.quiet,parse = args.parse, with_stdErr=True)
|
837
|
-
|
838
|
-
if __name__ == '__main__':
|
839
|
-
main()
|
@@ -0,0 +1,6 @@
|
|
1
|
+
multiCMD.py,sha256=GDefhp2vHOBFgiBmgIFa9Xls7EK95fDtuKWNW-tdopE,30274
|
2
|
+
multicmd-1.34.dist-info/METADATA,sha256=AaZBveq1NASBsdNBiUSSJFVAZ6ddb06MYM26C6t8NUY,5640
|
3
|
+
multicmd-1.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
+
multicmd-1.34.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
5
|
+
multicmd-1.34.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
6
|
+
multicmd-1.34.dist-info/RECORD,,
|
multicmd-1.32.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
multiCMD.py,sha256=G93d2ZTg4UJGiXqHlVxtvE6wfsy9S0VXB_Yp7J-XPY4,27942
|
2
|
-
multicmd-1.32.dist-info/METADATA,sha256=t-hLoingJ1P6oMPIJVKnLSWQsWMKiPUzwW9N0XfDL_g,5640
|
3
|
-
multicmd-1.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
-
multicmd-1.32.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
5
|
-
multicmd-1.32.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
6
|
-
multicmd-1.32.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|