multiCMD 1.13__py3-none-any.whl → 1.15__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-1.13.dist-info → multiCMD-1.15.dist-info}/METADATA +10 -3
- multiCMD-1.15.dist-info/RECORD +7 -0
- multiCMD.py +107 -11
- multiCMD-1.13.dist-info/RECORD +0 -7
- {multiCMD-1.13.dist-info → multiCMD-1.15.dist-info}/LICENSE +0 -0
- {multiCMD-1.13.dist-info → multiCMD-1.15.dist-info}/WHEEL +0 -0
- {multiCMD-1.13.dist-info → multiCMD-1.15.dist-info}/entry_points.txt +0 -0
- {multiCMD-1.13.dist-info → multiCMD-1.15.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: multiCMD
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.15
|
4
4
|
Summary: Run commands simultaneously
|
5
5
|
Home-page: https://github.com/yufei-pan/multiCMD
|
6
6
|
Author: Yufei Pan
|
@@ -8,6 +8,7 @@ Author-email: pan@zopyr.us
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
10
10
|
Classifier: Operating System :: POSIX :: Linux
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
11
12
|
Requires-Python: >=3.6
|
12
13
|
Description-Content-Type: text/markdown
|
13
14
|
License-File: LICENSE
|
@@ -90,7 +91,7 @@ def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,wit
|
|
90
91
|
max_threads: The maximum number of threads to use
|
91
92
|
quiet: Whether to suppress output
|
92
93
|
dry_run: Whether to simulate running the commands
|
93
|
-
with_stdErr: Whether to
|
94
|
+
with_stdErr: Whether to append the standard error output to the standard output
|
94
95
|
return_code_only: Whether to return only the return code
|
95
96
|
return_object: Whether to return the Task object
|
96
97
|
|
@@ -108,7 +109,7 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
|
|
108
109
|
max_threads: The maximum number of threads to use
|
109
110
|
quiet: Whether to suppress output
|
110
111
|
dry_run: Whether to simulate running the command
|
111
|
-
with_stdErr: Whether to
|
112
|
+
with_stdErr: Whether to append the standard error output to the standard output
|
112
113
|
return_code_only: Whether to return only the return code
|
113
114
|
return_object: Whether to return the Task object
|
114
115
|
|
@@ -160,4 +161,10 @@ def int_to_color(n, brightness_threshold=500):
|
|
160
161
|
@returns:
|
161
162
|
(int,int,int): The RGB color
|
162
163
|
'''
|
164
|
+
class Task:
|
165
|
+
def __init__(self, command):
|
166
|
+
self.command = command
|
167
|
+
self.returncode = None
|
168
|
+
self.stdout = []
|
169
|
+
self.stderr = []
|
163
170
|
```
|
@@ -0,0 +1,7 @@
|
|
1
|
+
multiCMD.py,sha256=WlB4sRghGzDCPJOR-4ZRlykC-PattkPt67xv0cCQr7E,15268
|
2
|
+
multiCMD-1.15.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
3
|
+
multiCMD-1.15.dist-info/METADATA,sha256=jPEDesAqHHrgA3DagJKLbaHIqLZu3h2jDPHs-4eS5F0,5008
|
4
|
+
multiCMD-1.15.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
5
|
+
multiCMD-1.15.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
6
|
+
multiCMD-1.15.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
7
|
+
multiCMD-1.15.dist-info/RECORD,,
|
multiCMD.py
CHANGED
@@ -7,8 +7,11 @@ import sys
|
|
7
7
|
import subprocess
|
8
8
|
import select
|
9
9
|
import os
|
10
|
+
import string
|
11
|
+
import re
|
12
|
+
import itertools
|
10
13
|
|
11
|
-
version = '1.
|
14
|
+
version = '1.15'
|
12
15
|
__version__ = version
|
13
16
|
|
14
17
|
class Task:
|
@@ -20,6 +23,60 @@ class Task:
|
|
20
23
|
def __iter__(self):
|
21
24
|
return zip(['command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
22
25
|
|
26
|
+
def _expand_ranges(inStr):
|
27
|
+
'''
|
28
|
+
Expand ranges in a string
|
29
|
+
|
30
|
+
@params:
|
31
|
+
inStr: The string to expand
|
32
|
+
|
33
|
+
@returns:
|
34
|
+
list[str]: The expanded string
|
35
|
+
'''
|
36
|
+
expandingStr = [inStr]
|
37
|
+
expandedList = []
|
38
|
+
# all valid alphanumeric characters
|
39
|
+
alphanumeric = string.digits + string.ascii_letters
|
40
|
+
while len(expandingStr) > 0:
|
41
|
+
currentStr = expandingStr.pop()
|
42
|
+
match = re.search(r'\[(.*?)]', currentStr)
|
43
|
+
if not match:
|
44
|
+
expandedList.append(currentStr)
|
45
|
+
continue
|
46
|
+
group = match.group(1)
|
47
|
+
parts = group.split(',')
|
48
|
+
for part in parts:
|
49
|
+
part = part.strip()
|
50
|
+
if '-' in part:
|
51
|
+
try:
|
52
|
+
range_start,_, range_end = part.partition('-')
|
53
|
+
except ValueError:
|
54
|
+
expandedList.append(currentStr)
|
55
|
+
continue
|
56
|
+
range_start = range_start.strip()
|
57
|
+
range_end = range_end.strip()
|
58
|
+
if range_start.isdigit() and range_end.isdigit():
|
59
|
+
padding_length = min(len(range_start), len(range_end))
|
60
|
+
format_str = "{:0" + str(padding_length) + "d}"
|
61
|
+
for i in range(int(range_start), int(range_end) + 1):
|
62
|
+
formatted_i = format_str.format(i)
|
63
|
+
expandingStr.append(currentStr.replace(match.group(0), formatted_i, 1))
|
64
|
+
elif all(c in string.hexdigits for c in range_start + range_end):
|
65
|
+
for i in range(int(range_start, 16), int(range_end, 16) + 1):
|
66
|
+
expandingStr.append(currentStr.replace(match.group(0), format(i, 'x'), 1))
|
67
|
+
else:
|
68
|
+
try:
|
69
|
+
start_index = alphanumeric.index(range_start)
|
70
|
+
end_index = alphanumeric.index(range_end)
|
71
|
+
for i in range(start_index, end_index + 1):
|
72
|
+
expandingStr.append(currentStr.replace(match.group(0), alphanumeric[i], 1))
|
73
|
+
except ValueError:
|
74
|
+
expandedList.append(currentStr)
|
75
|
+
else:
|
76
|
+
expandingStr.append(currentStr.replace(match.group(0), part, 1))
|
77
|
+
expandedList.reverse()
|
78
|
+
return expandedList
|
79
|
+
|
23
80
|
def __handle_stream(stream,target,pre='',post='',quiet=False):
|
24
81
|
'''
|
25
82
|
Handle a stream
|
@@ -173,7 +230,7 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
|
|
173
230
|
max_threads: The maximum number of threads to use
|
174
231
|
quiet: Whether to suppress output
|
175
232
|
dry_run: Whether to simulate running the command
|
176
|
-
with_stdErr: Whether to
|
233
|
+
with_stdErr: Whether to append the standard error output to the standard output
|
177
234
|
return_code_only: Whether to return only the return code
|
178
235
|
return_object: Whether to return the Task object
|
179
236
|
|
@@ -182,31 +239,71 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
|
|
182
239
|
'''
|
183
240
|
return run_commands([command], timeout, max_threads, quiet, dry_run, with_stdErr, return_code_only, return_object)[0]
|
184
241
|
|
242
|
+
def __format_command(command,expand = False):
|
243
|
+
'''
|
244
|
+
Format a command
|
245
|
+
|
246
|
+
@params:
|
247
|
+
command: The command to format
|
248
|
+
expand: Whether to expand ranges
|
249
|
+
|
250
|
+
@returns:
|
251
|
+
list[list[str]]: The formatted commands sequence
|
252
|
+
'''
|
253
|
+
if isinstance(command,str):
|
254
|
+
if expand:
|
255
|
+
commands = _expand_ranges(command)
|
256
|
+
else:
|
257
|
+
commands = [command]
|
258
|
+
return [command.split() for command in commands]
|
259
|
+
# elif it is a iterable
|
260
|
+
elif hasattr(command,'__iter__'):
|
261
|
+
sanitized_command = []
|
262
|
+
for field in command:
|
263
|
+
if isinstance(field,str):
|
264
|
+
sanitized_command.append(field)
|
265
|
+
else:
|
266
|
+
sanitized_command.append(repr(field))
|
267
|
+
if not expand:
|
268
|
+
return [sanitized_command]
|
269
|
+
sanitized_expanded_command = [_expand_ranges(field) for field in sanitized_command]
|
270
|
+
# now the command had been expanded to list of list of fields with each field expanded to all possible options
|
271
|
+
# we need to generate all possible combinations of the fields
|
272
|
+
# we will use the cartesian product to do this
|
273
|
+
commands = list(itertools.product(*sanitized_expanded_command))
|
274
|
+
return [list(command) for command in commands]
|
275
|
+
else:
|
276
|
+
return __format_command(str(command),expand=expand)
|
277
|
+
|
278
|
+
|
185
279
|
def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
|
186
|
-
return_code_only=False,return_object=False):
|
280
|
+
return_code_only=False,return_object=False, parse = False):
|
187
281
|
'''
|
188
282
|
Run multiple commands in parallel
|
189
283
|
|
190
284
|
@params:
|
191
|
-
commands: A list of commands to run
|
285
|
+
commands: A list of commands to run ( list[str] | list[list[str]] )
|
192
286
|
timeout: The timeout for each command
|
193
287
|
max_threads: The maximum number of threads to use
|
194
288
|
quiet: Whether to suppress output
|
195
289
|
dry_run: Whether to simulate running the commands
|
196
|
-
with_stdErr: Whether to
|
290
|
+
with_stdErr: Whether to append the standard error output to the standard output
|
197
291
|
return_code_only: Whether to return only the return code
|
198
292
|
return_object: Whether to return the Task object
|
293
|
+
parse: Whether to parse ranged input
|
199
294
|
|
200
295
|
@returns:
|
201
296
|
list: The output of the commands ( list[None] | list[int] | list[list[str]] | list[Task] )
|
202
297
|
'''
|
203
298
|
# split the commands in commands if it is a string
|
204
|
-
|
299
|
+
formatedCommands = []
|
300
|
+
for command in commands:
|
301
|
+
formatedCommands.extend(__format_command(command,expand=parse))
|
205
302
|
# initialize the tasks
|
206
|
-
tasks = [Task(command) for command in
|
303
|
+
tasks = [Task(command) for command in formatedCommands]
|
207
304
|
# run the tasks with max_threads. if max_threads is 0, use the number of commands
|
208
305
|
if max_threads < 1:
|
209
|
-
max_threads = len(
|
306
|
+
max_threads = len(formatedCommands)
|
210
307
|
if max_threads > 1:
|
211
308
|
sem = threading.Semaphore(max_threads) # Limit concurrent sessions
|
212
309
|
threads = [threading.Thread(target=__run_command, args=(task,sem,timeout,quiet,dry_run,...)) for task in tasks]
|
@@ -361,17 +458,16 @@ def print_progress_bar(iteration, total, prefix='', suffix=''):
|
|
361
458
|
if iteration % 5 == 0:
|
362
459
|
print(_genrate_progress_bar(iteration, total, prefix, suffix))
|
363
460
|
|
364
|
-
|
365
461
|
def main():
|
366
462
|
parser = argparse.ArgumentParser(description='Run multiple commands in parallel')
|
367
463
|
parser.add_argument('commands', metavar='command', type=str, nargs='+',help='commands to run')
|
368
|
-
|
464
|
+
parser.add_argument('-p','--parse', action='store_true',help='Parse ranged input and expand them into multiple commands')
|
369
465
|
parser.add_argument('-t','--timeout', metavar='timeout', type=int, default=60,help='timeout for each command')
|
370
466
|
parser.add_argument('-m','--max_threads', metavar='max_threads', type=int, default=1,help='maximum number of threads to use')
|
371
467
|
parser.add_argument('-q','--quiet', action='store_true',help='quiet mode')
|
372
468
|
parser.add_argument('-V','--version', action='version', version=f'%(prog)s {version} by pan@zopyr.us')
|
373
469
|
args = parser.parse_args()
|
374
|
-
run_commands(args.commands, args.timeout, args.max_threads, args.quiet)
|
470
|
+
run_commands(args.commands, args.timeout, args.max_threads, args.quiet,parse = args.parse)
|
375
471
|
|
376
472
|
if __name__ == '__main__':
|
377
473
|
main()
|
multiCMD-1.13.dist-info/RECORD
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
multiCMD.py,sha256=_agL0neMTFIiApwMJ-Pg0Sygc267k1qbaGku0ROP34E,12082
|
2
|
-
multiCMD-1.13.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
3
|
-
multiCMD-1.13.dist-info/METADATA,sha256=RraYvntxzCblSCs8ccYpsR6ISfnjn71V6ik3XO3ih4M,4772
|
4
|
-
multiCMD-1.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
5
|
-
multiCMD-1.13.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
6
|
-
multiCMD-1.13.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
7
|
-
multiCMD-1.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|