pyallel 0.13.0__tar.gz → 0.14.0__tar.gz
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.
- {pyallel-0.13.0 → pyallel-0.14.0}/PKG-INFO +1 -1
- {pyallel-0.13.0 → pyallel-0.14.0}/pyproject.toml +1 -1
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/main.py +2 -4
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/process.py +114 -79
- {pyallel-0.13.0 → pyallel-0.14.0}/LICENSE +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/README.md +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/__init__.py +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/constants.py +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/errors.py +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/parser.py +0 -0
- {pyallel-0.13.0 → pyallel-0.14.0}/src/pyallel/py.typed +0 -0
|
@@ -51,14 +51,12 @@ def run(*args: str) -> int:
|
|
|
51
51
|
exit_code = 1
|
|
52
52
|
message = traceback.format_exc()
|
|
53
53
|
|
|
54
|
-
if exit_code ==
|
|
55
|
-
print(f"{constants.YELLOW_BOLD}Interrupt!{constants.RESET_COLOUR}")
|
|
56
|
-
elif exit_code == 1:
|
|
54
|
+
if exit_code == 1:
|
|
57
55
|
if not message:
|
|
58
56
|
print(f"{constants.RED_BOLD}A command failed!{constants.RESET_COLOUR}")
|
|
59
57
|
else:
|
|
60
58
|
print(f"{constants.RED_BOLD}Error: {message}{constants.RESET_COLOUR}")
|
|
61
|
-
|
|
59
|
+
elif exit_code == 0:
|
|
62
60
|
print(f"{constants.GREEN_BOLD}Success!{constants.RESET_COLOUR}")
|
|
63
61
|
|
|
64
62
|
return exit_code
|
|
@@ -9,7 +9,7 @@ import shlex
|
|
|
9
9
|
import shutil
|
|
10
10
|
import os
|
|
11
11
|
from uuid import UUID, uuid4
|
|
12
|
-
from typing import BinaryIO
|
|
12
|
+
from typing import Any, BinaryIO
|
|
13
13
|
from pyallel import constants
|
|
14
14
|
|
|
15
15
|
from dataclasses import dataclass, field
|
|
@@ -83,6 +83,8 @@ class ProcessGroup:
|
|
|
83
83
|
output: dict[UUID, list[str]] = field(default_factory=lambda: defaultdict(list))
|
|
84
84
|
process_lines: list[int] = field(default_factory=list)
|
|
85
85
|
completed_processes: set[UUID] = field(default_factory=set)
|
|
86
|
+
exit_code: int = 0
|
|
87
|
+
interrupt_count: int = 0
|
|
86
88
|
passed: bool = True
|
|
87
89
|
icon: int = 0
|
|
88
90
|
|
|
@@ -93,36 +95,31 @@ class ProcessGroup:
|
|
|
93
95
|
if not self.interactive:
|
|
94
96
|
return self.stream_non_interactive()
|
|
95
97
|
|
|
96
|
-
interrupted = False
|
|
97
|
-
|
|
98
98
|
while True:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self.icon = 0
|
|
99
|
+
output = self.complete_output()
|
|
100
|
+
self.icon += 1
|
|
101
|
+
if self.icon == len(constants.ICONS):
|
|
102
|
+
self.icon = 0
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
# Clear the screen and print the output
|
|
105
|
+
print(f"\033[H\033[0J{output}", end="")
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
# Clear the screen again
|
|
108
|
+
print("\033[H\033[0J", end="")
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
if len(self.completed_processes) == len(self.processes):
|
|
111
|
+
break
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
except KeyboardInterrupt:
|
|
116
|
-
interrupted = True
|
|
117
|
-
for process in self.processes:
|
|
118
|
-
process.interrupt()
|
|
119
|
-
process.wait()
|
|
113
|
+
time.sleep(0.1)
|
|
120
114
|
|
|
121
115
|
output = self.complete_output(all=True)
|
|
122
116
|
# Clear the screen one final time before printing the output
|
|
123
117
|
print(f"\033[3J{output}")
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
if not self.exit_code and not self.passed:
|
|
120
|
+
self.exit_code = 1
|
|
121
|
+
|
|
122
|
+
return self.exit_code
|
|
126
123
|
|
|
127
124
|
def stream_non_interactive(self) -> int:
|
|
128
125
|
running_process = None
|
|
@@ -131,67 +128,88 @@ class ProcessGroup:
|
|
|
131
128
|
print(f"{constants.WHITE_BOLD}Running commands...{constants.RESET_COLOUR}\n")
|
|
132
129
|
|
|
133
130
|
while True:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
output = ""
|
|
132
|
+
for process in self.processes:
|
|
133
|
+
if (
|
|
134
|
+
running_process is None
|
|
135
|
+
and process.id not in self.completed_processes
|
|
136
|
+
):
|
|
137
|
+
output += get_command_status(process, verbose=self.verbose)
|
|
138
|
+
output += "\n"
|
|
139
|
+
running_process = process
|
|
140
|
+
elif running_process is not process:
|
|
141
|
+
# Need to do this to properly keep track of how long all the other
|
|
142
|
+
# commands are taking
|
|
143
|
+
process.poll()
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
process_output = process.readline().decode()
|
|
147
|
+
|
|
148
|
+
if not self.output[process.id] and process_output:
|
|
149
|
+
process_output = prefix(process_output)
|
|
150
|
+
self.output[process.id].append(process_output)
|
|
151
|
+
output += process_output
|
|
152
|
+
elif process_output:
|
|
153
|
+
if self.output[process.id][-1][-1] != "\n":
|
|
154
|
+
self.output[process.id][-1] += process_output
|
|
155
|
+
else:
|
|
156
|
+
process_output = prefix(process_output)
|
|
157
|
+
self.output[process.id].append(process_output)
|
|
158
|
+
output += process_output
|
|
159
|
+
|
|
160
|
+
if process.poll() is not None:
|
|
161
|
+
if process.return_code() != 0:
|
|
162
|
+
self.passed = False
|
|
163
|
+
process_output = process.read().decode()
|
|
164
|
+
if process_output:
|
|
165
|
+
output += prefix(process_output)
|
|
166
|
+
|
|
167
|
+
output += get_command_status(
|
|
168
|
+
process,
|
|
169
|
+
passed=process.return_code() == 0,
|
|
170
|
+
verbose=self.verbose,
|
|
171
|
+
timer=self.timer,
|
|
172
|
+
)
|
|
173
|
+
output += "\n\n"
|
|
174
|
+
self.completed_processes.add(process.id)
|
|
175
|
+
running_process = None
|
|
176
|
+
|
|
177
|
+
if self.interrupt_count == 0:
|
|
178
|
+
pass
|
|
179
|
+
elif not interrupted and self.interrupt_count == 1:
|
|
180
|
+
if (output and output[-1] != "\n") or (
|
|
181
|
+
self.output[process.id]
|
|
182
|
+
and self.output[process.id][-1][-1] != "\n"
|
|
140
183
|
):
|
|
141
|
-
output += get_command_status(process, verbose=self.verbose)
|
|
142
184
|
output += "\n"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# Need to do this to properly keep track of how long all the other
|
|
146
|
-
# commands are taking
|
|
147
|
-
process.poll()
|
|
148
|
-
continue
|
|
185
|
+
output += f"\n{constants.YELLOW_BOLD}Interrupt!{constants.RESET_COLOUR}\n\n"
|
|
186
|
+
interrupted = True
|
|
149
187
|
|
|
150
|
-
|
|
188
|
+
if output:
|
|
189
|
+
print(output, end="")
|
|
151
190
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
verbose=self.verbose,
|
|
175
|
-
timer=self.timer,
|
|
176
|
-
)
|
|
177
|
-
output += "\n\n"
|
|
178
|
-
self.completed_processes.add(process.id)
|
|
179
|
-
running_process = None
|
|
180
|
-
|
|
181
|
-
if output:
|
|
182
|
-
print(output, end="")
|
|
183
|
-
|
|
184
|
-
if len(self.completed_processes) == len(self.processes):
|
|
185
|
-
break
|
|
186
|
-
|
|
187
|
-
time.sleep(0.01)
|
|
188
|
-
except KeyboardInterrupt:
|
|
189
|
-
interrupted = True
|
|
190
|
-
for process in self.processes:
|
|
191
|
-
process.interrupt()
|
|
192
|
-
process.wait()
|
|
193
|
-
|
|
194
|
-
return 2 if interrupted else (1 if not self.passed else 0)
|
|
191
|
+
if len(self.completed_processes) == len(self.processes):
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
time.sleep(0.01)
|
|
195
|
+
|
|
196
|
+
if self.interrupt_count == 2:
|
|
197
|
+
print(f"{constants.RED_BOLD}Abort!{constants.RESET_COLOUR}")
|
|
198
|
+
|
|
199
|
+
if not self.exit_code and not self.passed:
|
|
200
|
+
self.exit_code = 1
|
|
201
|
+
|
|
202
|
+
return self.exit_code
|
|
203
|
+
|
|
204
|
+
def _handle_signal(self, signum: int, _frame: Any) -> None:
|
|
205
|
+
for process in self.processes:
|
|
206
|
+
if self.interrupt_count == 0:
|
|
207
|
+
process.interrupt()
|
|
208
|
+
else:
|
|
209
|
+
process.kill()
|
|
210
|
+
|
|
211
|
+
self.exit_code = 128 + signum
|
|
212
|
+
self.interrupt_count += 1
|
|
195
213
|
|
|
196
214
|
@classmethod
|
|
197
215
|
def from_commands(
|
|
@@ -213,13 +231,18 @@ class ProcessGroup:
|
|
|
213
231
|
if errors:
|
|
214
232
|
raise InvalidExecutableErrors(*errors)
|
|
215
233
|
|
|
216
|
-
|
|
234
|
+
process_group = cls(
|
|
217
235
|
processes=processes,
|
|
218
236
|
interactive=interactive,
|
|
219
237
|
timer=timer,
|
|
220
238
|
verbose=verbose,
|
|
221
239
|
)
|
|
222
240
|
|
|
241
|
+
signal.signal(signal.SIGINT, process_group._handle_signal)
|
|
242
|
+
signal.signal(signal.SIGTERM, process_group._handle_signal)
|
|
243
|
+
|
|
244
|
+
return process_group
|
|
245
|
+
|
|
223
246
|
def complete_output(self, tail: int = 20, all: bool = False) -> str:
|
|
224
247
|
num_processes = len(self.processes)
|
|
225
248
|
lines = constants.LINES() - (2 * num_processes)
|
|
@@ -276,6 +299,14 @@ class ProcessGroup:
|
|
|
276
299
|
if i != num_processes:
|
|
277
300
|
output += "\n"
|
|
278
301
|
|
|
302
|
+
if self.interrupt_count == 0:
|
|
303
|
+
return output
|
|
304
|
+
|
|
305
|
+
if self.interrupt_count == 1:
|
|
306
|
+
output += f"\n{constants.YELLOW_BOLD}Interrupt!{constants.RESET_COLOUR}"
|
|
307
|
+
elif self.interrupt_count == 2:
|
|
308
|
+
output += f"\n{constants.RED_BOLD}Abort!{constants.RESET_COLOUR}"
|
|
309
|
+
|
|
279
310
|
return output
|
|
280
311
|
|
|
281
312
|
|
|
@@ -341,6 +372,10 @@ class Process:
|
|
|
341
372
|
if self._process:
|
|
342
373
|
self._process.send_signal(signal.SIGINT)
|
|
343
374
|
|
|
375
|
+
def kill(self) -> None:
|
|
376
|
+
if self._process:
|
|
377
|
+
self._process.send_signal(signal.SIGKILL)
|
|
378
|
+
|
|
344
379
|
def wait(self) -> int:
|
|
345
380
|
if self._process:
|
|
346
381
|
return self._process.wait()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|