pyallel 1.3.2__tar.gz → 1.3.4__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-1.3.2 → pyallel-1.3.4}/PKG-INFO +2 -1
- {pyallel-1.3.2 → pyallel-1.3.4}/README.md +1 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/pyproject.toml +1 -1
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/constants.py +4 -1
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/main.py +2 -2
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/printer.py +80 -23
- {pyallel-1.3.2 → pyallel-1.3.4}/LICENSE +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/__init__.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/colours.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/errors.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/parser.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/process.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/process_group.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/process_group_manager.py +0 -0
- {pyallel-1.3.2 → pyallel-1.3.4}/src/pyallel/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyallel
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Run and handle the output of multiple executables in pyallel (as in parallel)
|
|
5
5
|
Home-page: https://github.com/Danthewaann/pyallel
|
|
6
6
|
License: MIT
|
|
@@ -156,6 +156,7 @@ python -m venv .venv && \
|
|
|
156
156
|
- [x] Add support to have commands depend on other commands (some commands must complete
|
|
157
157
|
before a given command can start)
|
|
158
158
|
- [x] Add support to state how many lines a command can use for it's output in interactive mode
|
|
159
|
+
- [x] Improve printing of output performance by only printing lines that have changed
|
|
159
160
|
- [ ] Add a debug mode that logs debug information to a log file
|
|
160
161
|
- [ ] Maybe add support to allow the user to provide stdin for commands that request it
|
|
161
162
|
(such as a REPL)
|
|
@@ -132,6 +132,7 @@ python -m venv .venv && \
|
|
|
132
132
|
- [x] Add support to have commands depend on other commands (some commands must complete
|
|
133
133
|
before a given command can start)
|
|
134
134
|
- [x] Add support to state how many lines a command can use for it's output in interactive mode
|
|
135
|
+
- [x] Improve printing of output performance by only printing lines that have changed
|
|
135
136
|
- [ ] Add a debug mode that logs debug information to a log file
|
|
136
137
|
- [ ] Maybe add support to allow the user to provide stdin for commands that request it
|
|
137
138
|
(such as a REPL)
|
|
@@ -3,8 +3,11 @@ import shutil
|
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
5
|
IN_TTY = sys.stdout.isatty()
|
|
6
|
+
|
|
7
|
+
# From: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
|
6
8
|
CLEAR_LINE = "\033[2K"
|
|
7
|
-
UP_LINE = "\033[
|
|
9
|
+
UP_LINE = "\033[1A\r"
|
|
10
|
+
DOWN_LINE = "\033[1B\r"
|
|
8
11
|
ANSI_ESCAPE = re.compile(r"(\x9B|\x1B\[|\x1B\()[0-?]*[ -\/]*[@-~]")
|
|
9
12
|
|
|
10
13
|
if IN_TTY:
|
|
@@ -19,7 +19,6 @@ def run_interactive(
|
|
|
19
19
|
while True:
|
|
20
20
|
process_group_manager.stream()
|
|
21
21
|
|
|
22
|
-
printer.clear_printed_lines()
|
|
23
22
|
output = process_group_manager.get_cur_process_group_output()
|
|
24
23
|
printer.print_progress_group_output(
|
|
25
24
|
output, process_group_manager._interrupt_count
|
|
@@ -27,7 +26,8 @@ def run_interactive(
|
|
|
27
26
|
|
|
28
27
|
poll = process_group_manager.poll()
|
|
29
28
|
if poll is not None:
|
|
30
|
-
printer.
|
|
29
|
+
printer.clear_last_printed_lines()
|
|
30
|
+
printer.reset()
|
|
31
31
|
printer.print_progress_group_output(
|
|
32
32
|
output, process_group_manager._interrupt_count, tail_output=False
|
|
33
33
|
)
|
|
@@ -14,7 +14,8 @@ class Printer:
|
|
|
14
14
|
self._timer = timer
|
|
15
15
|
self._prefix = f"{self._colours.dim_on}=>{self._colours.dim_off} "
|
|
16
16
|
self._icon = 0
|
|
17
|
-
self.
|
|
17
|
+
self._last_printed: list[tuple[bool, str, str]] = []
|
|
18
|
+
self._to_print: list[tuple[bool, str, str]] = []
|
|
18
19
|
|
|
19
20
|
def write(
|
|
20
21
|
self,
|
|
@@ -23,13 +24,15 @@ class Printer:
|
|
|
23
24
|
end: str = "\n",
|
|
24
25
|
flush: bool = False,
|
|
25
26
|
truncate: bool = False,
|
|
27
|
+
columns: int | None = None,
|
|
26
28
|
) -> None:
|
|
27
29
|
truncate_num = 0
|
|
28
30
|
prefix = self._prefix if include_prefix else ""
|
|
31
|
+
columns = columns or constants.COLUMNS()
|
|
29
32
|
if prefix:
|
|
30
33
|
truncate_num = 6
|
|
31
34
|
if truncate:
|
|
32
|
-
columns =
|
|
35
|
+
columns = columns - truncate_num
|
|
33
36
|
if get_num_lines(line, columns) > 1:
|
|
34
37
|
line = truncate_line(line, columns)
|
|
35
38
|
print(f"{prefix}{line}", end=end, flush=flush)
|
|
@@ -80,7 +83,7 @@ class Printer:
|
|
|
80
83
|
)
|
|
81
84
|
line_parts = (False, status, "\n")
|
|
82
85
|
out.append(line_parts)
|
|
83
|
-
self.
|
|
86
|
+
self._to_print.append(line_parts)
|
|
84
87
|
|
|
85
88
|
if include_output:
|
|
86
89
|
lines = output.data.splitlines(keepends=True)
|
|
@@ -101,7 +104,7 @@ class Printer:
|
|
|
101
104
|
line = line[:-1]
|
|
102
105
|
|
|
103
106
|
try:
|
|
104
|
-
prev_line = self.
|
|
107
|
+
prev_line = self._to_print[-1]
|
|
105
108
|
except IndexError:
|
|
106
109
|
pass
|
|
107
110
|
else:
|
|
@@ -110,7 +113,7 @@ class Printer:
|
|
|
110
113
|
|
|
111
114
|
line_parts = (prefix, line, end)
|
|
112
115
|
out.append(line_parts)
|
|
113
|
-
self.
|
|
116
|
+
self._to_print.append(line_parts)
|
|
114
117
|
|
|
115
118
|
return out
|
|
116
119
|
|
|
@@ -177,8 +180,8 @@ class Printer:
|
|
|
177
180
|
self.generate_process_output(out, tail_output, append_newlines=True)
|
|
178
181
|
|
|
179
182
|
if interrupt_count == 1:
|
|
180
|
-
self.
|
|
181
|
-
self.
|
|
183
|
+
self._to_print.append((False, "", "\n"))
|
|
184
|
+
self._to_print.append(
|
|
182
185
|
(
|
|
183
186
|
False,
|
|
184
187
|
f"{self._colours.yellow_bold}Interrupt!{self._colours.reset_colour}",
|
|
@@ -186,8 +189,8 @@ class Printer:
|
|
|
186
189
|
)
|
|
187
190
|
)
|
|
188
191
|
elif interrupt_count == 2:
|
|
189
|
-
self.
|
|
190
|
-
self.
|
|
192
|
+
self._to_print.append((False, "", "\n"))
|
|
193
|
+
self._to_print.append(
|
|
191
194
|
(
|
|
192
195
|
False,
|
|
193
196
|
f"{self._colours.red_bold}Abort!{self._colours.reset_colour}",
|
|
@@ -199,7 +202,7 @@ class Printer:
|
|
|
199
202
|
if self._icon == len(constants.ICONS):
|
|
200
203
|
self._icon = 0
|
|
201
204
|
|
|
202
|
-
return self.
|
|
205
|
+
return self._to_print
|
|
203
206
|
|
|
204
207
|
def print_process_output(
|
|
205
208
|
self,
|
|
@@ -221,7 +224,7 @@ class Printer:
|
|
|
221
224
|
self.write(line, include_prefix, end)
|
|
222
225
|
|
|
223
226
|
# Force a flush otherwise lines that don't end in a newline character will not get printed as they are read
|
|
224
|
-
|
|
227
|
+
print("", end="", flush=True)
|
|
225
228
|
|
|
226
229
|
def print_progress_group_output(
|
|
227
230
|
self,
|
|
@@ -229,24 +232,61 @@ class Printer:
|
|
|
229
232
|
interrupt_count: int = 0,
|
|
230
233
|
tail_output: bool = True,
|
|
231
234
|
) -> None:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
):
|
|
235
|
-
self.write(line, include_prefix, end, truncate=tail_output)
|
|
235
|
+
columns = constants.COLUMNS()
|
|
236
|
+
self.generate_process_group_output(output, interrupt_count, tail_output)
|
|
236
237
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
num_last_printed_lines = len(self._last_printed)
|
|
239
|
+
|
|
240
|
+
# If we don't have any last printed lines or we don't want to tail the output,
|
|
241
|
+
# we just print all the new lines
|
|
242
|
+
if not num_last_printed_lines or not tail_output:
|
|
243
|
+
for include_prefix, line, end in self._to_print:
|
|
241
244
|
self.write(
|
|
242
|
-
|
|
243
|
-
end="",
|
|
245
|
+
line, include_prefix, end, truncate=tail_output, columns=columns
|
|
244
246
|
)
|
|
247
|
+
else:
|
|
248
|
+
# Compare the number of last lines and new lines and only update what has changed.
|
|
249
|
+
#
|
|
250
|
+
# Move the cursor up the amount the lines that were last printed so we can start
|
|
251
|
+
# comparing the last printed lines with the new lines that were generated
|
|
252
|
+
print(f"\033[{num_last_printed_lines}A", end="")
|
|
253
|
+
for i, line_parts in enumerate(self._to_print):
|
|
254
|
+
# If this is a completely new line, just print it
|
|
255
|
+
if i >= num_last_printed_lines:
|
|
256
|
+
include_prefix, line, end = line_parts
|
|
257
|
+
self.write(
|
|
258
|
+
line, include_prefix, end, truncate=tail_output, columns=columns
|
|
259
|
+
)
|
|
260
|
+
# If the current line is not the same as it's newly generated version, we update the line
|
|
261
|
+
elif line_parts[1] != self._last_printed[i][1]:
|
|
262
|
+
include_prefix, line, end = line_parts
|
|
263
|
+
# Clear the current line
|
|
264
|
+
print(f"{constants.CLEAR_LINE}\r", end="")
|
|
265
|
+
# Write the new line, this will move the cursor to the next line automatically
|
|
266
|
+
self.write(
|
|
267
|
+
line, include_prefix, end, truncate=tail_output, columns=columns
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
# Move on to the next line as this one doesn't need to be updated
|
|
271
|
+
print(constants.DOWN_LINE, end="")
|
|
272
|
+
|
|
273
|
+
# Force a flush to return the cursor to the bottom immediately
|
|
274
|
+
print("", end="", flush=True)
|
|
245
275
|
|
|
246
|
-
self.
|
|
276
|
+
self._last_printed = self._to_print.copy()
|
|
277
|
+
self._to_print.clear()
|
|
278
|
+
|
|
279
|
+
def clear_last_printed_lines(self) -> None:
|
|
280
|
+
# Clear all the lines that were just printed
|
|
281
|
+
print(
|
|
282
|
+
f"{constants.CLEAR_LINE}{constants.UP_LINE}{constants.CLEAR_LINE}"
|
|
283
|
+
* len(self._last_printed),
|
|
284
|
+
end="",
|
|
285
|
+
)
|
|
247
286
|
|
|
248
287
|
def reset(self) -> None:
|
|
249
|
-
self.
|
|
288
|
+
self._last_printed.clear()
|
|
289
|
+
self._to_print.clear()
|
|
250
290
|
|
|
251
291
|
|
|
252
292
|
def set_process_lines(
|
|
@@ -304,6 +344,23 @@ def set_process_lines(
|
|
|
304
344
|
# their terminal lines as normal and break out of the while loop
|
|
305
345
|
for process_output in processes_with_excess_output:
|
|
306
346
|
process_output.process.lines = allocated_process_lines
|
|
347
|
+
lines -= allocated_process_lines
|
|
348
|
+
|
|
349
|
+
# If there is any lines left, allocate them to the process that currently contains the most lines in its output, or
|
|
350
|
+
# allocate them to the first process if no process contains enough lines
|
|
351
|
+
if lines:
|
|
352
|
+
process_with_most_lines: ProcessOutput | None = None
|
|
353
|
+
most_lines = 0
|
|
354
|
+
for process_output in output.processes:
|
|
355
|
+
if process_output.process.lines > most_lines:
|
|
356
|
+
process_with_most_lines = process_output
|
|
357
|
+
most_lines = process_output.process.lines
|
|
358
|
+
|
|
359
|
+
if not process_with_most_lines:
|
|
360
|
+
output.processes[0].process.lines += lines
|
|
361
|
+
else:
|
|
362
|
+
process_with_most_lines.process.lines += lines
|
|
363
|
+
|
|
307
364
|
break
|
|
308
365
|
|
|
309
366
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|