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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: multiCMD
3
- Version: 1.13
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 return the standard error output
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 return the standard error output
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.13'
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 return the standard error output
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 return the standard error output
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
- commands = [command.split() if isinstance(command,str) else command for command in commands]
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 commands]
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(commands)
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
- # parser.add_argument('-p','--parse', action='store_true',help='parse ranged input')
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()
@@ -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,,