pyallel 0.5.0__tar.gz → 0.6.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.5.0 → pyallel-0.6.0}/PKG-INFO +38 -12
- pyallel-0.6.0/README.md +83 -0
- {pyallel-0.5.0 → pyallel-0.6.0}/pyproject.toml +1 -1
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/main.py +7 -4
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/parser.py +23 -18
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/process.py +55 -22
- pyallel-0.5.0/README.md +0 -57
- {pyallel-0.5.0 → pyallel-0.6.0}/LICENSE +0 -0
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/__init__.py +0 -0
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/constants.py +0 -0
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/errors.py +0 -0
- {pyallel-0.5.0 → pyallel-0.6.0}/src/pyallel/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -23,6 +23,8 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
|
|
24
24
|
Run and handle the output of multiple executables in `pyallel` (as in parallel)
|
|
25
25
|
|
|
26
|
+
https://github.com/Danthewaann/pyallel/assets/22531177/be12efc4-439d-416d-8112-dc57cc4c291a
|
|
27
|
+
|
|
26
28
|
Requires Python >=3.8
|
|
27
29
|
|
|
28
30
|
Tested on Linux and MacOS only
|
|
@@ -37,17 +39,43 @@ pip install pyallel
|
|
|
37
39
|
|
|
38
40
|
Once installed, you can run `pyallel` to see usage information, like so:
|
|
39
41
|
|
|
40
|
-
```
|
|
41
|
-
pyallel
|
|
42
|
+
```
|
|
43
|
+
usage: pyallel [-h] [-d] [-n] [-s] [-V] [-v] [commands ...]
|
|
44
|
+
|
|
45
|
+
Run and handle the output of multiple executables in pyallel (as in parallel)
|
|
46
|
+
|
|
47
|
+
positional arguments:
|
|
48
|
+
commands list of quoted commands to run e.g "mypy ." "black ."
|
|
49
|
+
|
|
50
|
+
can provide environment variables to each command like so:
|
|
51
|
+
|
|
52
|
+
"MYPY_FORCE_COLOR=1 mypy ."
|
|
53
|
+
|
|
54
|
+
command modes:
|
|
55
|
+
|
|
56
|
+
can also provide modes to commands to do extra things:
|
|
57
|
+
|
|
58
|
+
"tail=10 :: pytest ." <-- only output the last 10 lines, doesn't work in --no-stream mode
|
|
59
|
+
|
|
60
|
+
options:
|
|
61
|
+
-h, --help show this help message and exit
|
|
62
|
+
-d, --debug output debug info for each command
|
|
63
|
+
-n, --non-interactive
|
|
64
|
+
run in non-interactive mode
|
|
65
|
+
-s, --no-stream don't stream output of each command
|
|
66
|
+
-V, --verbose run in verbose mode
|
|
67
|
+
-v, --version print version and exit
|
|
42
68
|
```
|
|
43
69
|
|
|
44
70
|
Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
|
|
45
71
|
|
|
46
72
|
> [!IMPORTANT]
|
|
47
|
-
> If
|
|
73
|
+
> If you need to provide arguments to a command, you must surround the command and it's arguments in quotes!
|
|
48
74
|
|
|
49
75
|
```bash
|
|
50
|
-
pyallel "
|
|
76
|
+
pyallel "MYPY_FORCE_COLOR=1 mypy ." \
|
|
77
|
+
"black --check --diff ." \
|
|
78
|
+
"tail=20 :: pytest ."
|
|
51
79
|
```
|
|
52
80
|
|
|
53
81
|
## TODOs
|
|
@@ -55,15 +83,13 @@ pyallel "black --color --check --diff ." "MYPY_FORCE_COLOR=1 mypy ." "ruff check
|
|
|
55
83
|
- [x] Allow output for all provided commands to be streamed to stdout (this will require a
|
|
56
84
|
re-work of how we print command output as we currently just print output once the command
|
|
57
85
|
finishes)
|
|
58
|
-
- [
|
|
59
|
-
- [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
This will require adding special arguments/modes to each command so they are treated
|
|
63
|
-
differently from other commands
|
|
64
|
-
- [ ] Add visual examples of `pyallel` in action
|
|
86
|
+
- [x] Add CI checks to run the tests and linters against Python versions > 3.8
|
|
87
|
+
- [x] Add command mode arguments to support things like only tailing the last 10 lines
|
|
88
|
+
of a command whilst it is running e.g. `"tail=10 :: pytest ."`
|
|
89
|
+
- [x] Add visual examples of `pyallel` in action
|
|
65
90
|
- [ ] Add custom parsing of command output to support filtering for errors (like vim's
|
|
66
91
|
`errorformat`)
|
|
92
|
+
- [ ] Add graceful Ctrl-C interrupt handling
|
|
67
93
|
- [x] Provide a way to set environment variables for each command to run with
|
|
68
94
|
- [ ] Allow list of files to be provided to supply as input arguments to each command
|
|
69
95
|
- [ ] Allow input to be piped into `pyallel` via stdin to supply as standard input to each
|
pyallel-0.6.0/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Pyallel
|
|
2
|
+
|
|
3
|
+
Run and handle the output of multiple executables in `pyallel` (as in parallel)
|
|
4
|
+
|
|
5
|
+
https://github.com/Danthewaann/pyallel/assets/22531177/be12efc4-439d-416d-8112-dc57cc4c291a
|
|
6
|
+
|
|
7
|
+
Requires Python >=3.8
|
|
8
|
+
|
|
9
|
+
Tested on Linux and MacOS only
|
|
10
|
+
|
|
11
|
+
# Quick start
|
|
12
|
+
|
|
13
|
+
`pyallel` can be installed using pip:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install pyallel
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Once installed, you can run `pyallel` to see usage information, like so:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
usage: pyallel [-h] [-d] [-n] [-s] [-V] [-v] [commands ...]
|
|
23
|
+
|
|
24
|
+
Run and handle the output of multiple executables in pyallel (as in parallel)
|
|
25
|
+
|
|
26
|
+
positional arguments:
|
|
27
|
+
commands list of quoted commands to run e.g "mypy ." "black ."
|
|
28
|
+
|
|
29
|
+
can provide environment variables to each command like so:
|
|
30
|
+
|
|
31
|
+
"MYPY_FORCE_COLOR=1 mypy ."
|
|
32
|
+
|
|
33
|
+
command modes:
|
|
34
|
+
|
|
35
|
+
can also provide modes to commands to do extra things:
|
|
36
|
+
|
|
37
|
+
"tail=10 :: pytest ." <-- only output the last 10 lines, doesn't work in --no-stream mode
|
|
38
|
+
|
|
39
|
+
options:
|
|
40
|
+
-h, --help show this help message and exit
|
|
41
|
+
-d, --debug output debug info for each command
|
|
42
|
+
-n, --non-interactive
|
|
43
|
+
run in non-interactive mode
|
|
44
|
+
-s, --no-stream don't stream output of each command
|
|
45
|
+
-V, --verbose run in verbose mode
|
|
46
|
+
-v, --version print version and exit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
|
|
50
|
+
|
|
51
|
+
> [!IMPORTANT]
|
|
52
|
+
> If you need to provide arguments to a command, you must surround the command and it's arguments in quotes!
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pyallel "MYPY_FORCE_COLOR=1 mypy ." \
|
|
56
|
+
"black --check --diff ." \
|
|
57
|
+
"tail=20 :: pytest ."
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## TODOs
|
|
61
|
+
|
|
62
|
+
- [x] Allow output for all provided commands to be streamed to stdout (this will require a
|
|
63
|
+
re-work of how we print command output as we currently just print output once the command
|
|
64
|
+
finishes)
|
|
65
|
+
- [x] Add CI checks to run the tests and linters against Python versions > 3.8
|
|
66
|
+
- [x] Add command mode arguments to support things like only tailing the last 10 lines
|
|
67
|
+
of a command whilst it is running e.g. `"tail=10 :: pytest ."`
|
|
68
|
+
- [x] Add visual examples of `pyallel` in action
|
|
69
|
+
- [ ] Add custom parsing of command output to support filtering for errors (like vim's
|
|
70
|
+
`errorformat`)
|
|
71
|
+
- [ ] Add graceful Ctrl-C interrupt handling
|
|
72
|
+
- [x] Provide a way to set environment variables for each command to run with
|
|
73
|
+
- [ ] Allow list of files to be provided to supply as input arguments to each command
|
|
74
|
+
- [ ] Allow input to be piped into `pyallel` via stdin to supply as standard input to each
|
|
75
|
+
command
|
|
76
|
+
- [ ] Add custom config file for `pyallel` to read from as an alternative to providing
|
|
77
|
+
arguments via the command line
|
|
78
|
+
- [ ] Add support for providing config via a `[tool.pyallel]` section in a
|
|
79
|
+
`pyproject.toml` file in the current working directory
|
|
80
|
+
- [ ] Maybe allow command dependencies to be defined in a python file where commands are
|
|
81
|
+
decorated with info that details it's dependencies?
|
|
82
|
+
- [x] Add test suite
|
|
83
|
+
- [x] Improve error handling when parsing provided commands (check they are valid executables)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
import traceback
|
|
4
5
|
import time
|
|
5
6
|
import importlib.metadata
|
|
7
|
+
from pyallel.errors import InvalidExecutableErrors
|
|
6
8
|
|
|
7
9
|
from pyallel.parser import Arguments, create_parser
|
|
8
10
|
from pyallel.process import ProcessGroup, format_time_taken
|
|
@@ -11,13 +13,12 @@ from pyallel import constants
|
|
|
11
13
|
|
|
12
14
|
def main_loop(
|
|
13
15
|
commands: list[str],
|
|
14
|
-
fail_fast: bool = False,
|
|
15
16
|
interactive: bool = False,
|
|
16
17
|
debug: bool = False,
|
|
17
18
|
stream: bool = False,
|
|
18
19
|
) -> bool:
|
|
19
20
|
process_group = ProcessGroup.from_commands(
|
|
20
|
-
commands, interactive=interactive,
|
|
21
|
+
commands, interactive=interactive, debug=debug
|
|
21
22
|
)
|
|
22
23
|
if not stream:
|
|
23
24
|
return process_group.run()
|
|
@@ -48,14 +49,16 @@ def run(*args: str) -> int:
|
|
|
48
49
|
try:
|
|
49
50
|
status = main_loop(
|
|
50
51
|
parsed_args.commands,
|
|
51
|
-
parsed_args.fail_fast,
|
|
52
52
|
parsed_args.interactive,
|
|
53
53
|
parsed_args.debug,
|
|
54
54
|
parsed_args.stream,
|
|
55
55
|
)
|
|
56
|
-
except
|
|
56
|
+
except InvalidExecutableErrors as e:
|
|
57
57
|
status = False
|
|
58
58
|
message = str(e)
|
|
59
|
+
except Exception:
|
|
60
|
+
status = False
|
|
61
|
+
message = traceback.format_exc()
|
|
59
62
|
|
|
60
63
|
if not status:
|
|
61
64
|
if not message:
|
|
@@ -5,7 +5,6 @@ from argparse import ArgumentParser, RawTextHelpFormatter
|
|
|
5
5
|
|
|
6
6
|
class Arguments:
|
|
7
7
|
commands: list[str]
|
|
8
|
-
fail_fast: bool
|
|
9
8
|
interactive: bool
|
|
10
9
|
debug: bool
|
|
11
10
|
verbose: bool
|
|
@@ -20,6 +19,20 @@ class Arguments:
|
|
|
20
19
|
return msg
|
|
21
20
|
|
|
22
21
|
|
|
22
|
+
COMMANDS_HELP = """list of quoted commands to run e.g "mypy ." "black ."
|
|
23
|
+
|
|
24
|
+
can provide environment variables to each command like so:
|
|
25
|
+
|
|
26
|
+
"MYPY_FORCE_COLOR=1 mypy ."
|
|
27
|
+
|
|
28
|
+
command modes:
|
|
29
|
+
|
|
30
|
+
can also provide modes to commands to do extra things:
|
|
31
|
+
|
|
32
|
+
"tail=10 :: pytest ." <-- only output the last 10 lines, doesn't work in --no-stream mode
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
23
36
|
def create_parser() -> ArgumentParser:
|
|
24
37
|
parser = ArgumentParser(
|
|
25
38
|
prog="pyallel",
|
|
@@ -28,15 +41,13 @@ def create_parser() -> ArgumentParser:
|
|
|
28
41
|
)
|
|
29
42
|
parser.add_argument(
|
|
30
43
|
"commands",
|
|
31
|
-
help=
|
|
32
|
-
"can provide environment variables to each command like so:\n\n"
|
|
33
|
-
' "MYPY_FORCE_COLOR=1 mypy ."',
|
|
44
|
+
help=COMMANDS_HELP,
|
|
34
45
|
nargs="*",
|
|
35
46
|
)
|
|
36
47
|
parser.add_argument(
|
|
37
|
-
"-
|
|
38
|
-
"--
|
|
39
|
-
help="
|
|
48
|
+
"-d",
|
|
49
|
+
"--debug",
|
|
50
|
+
help="output debug info for each command",
|
|
40
51
|
action="store_true",
|
|
41
52
|
default=False,
|
|
42
53
|
)
|
|
@@ -48,19 +59,13 @@ def create_parser() -> ArgumentParser:
|
|
|
48
59
|
dest="interactive",
|
|
49
60
|
default=True,
|
|
50
61
|
)
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
"-d",
|
|
53
|
-
"--debug",
|
|
54
|
-
help="output debug info for each command",
|
|
55
|
-
action="store_true",
|
|
56
|
-
default=False,
|
|
57
|
-
)
|
|
58
62
|
parser.add_argument(
|
|
59
63
|
"-s",
|
|
60
|
-
"--stream",
|
|
61
|
-
help="stream output of
|
|
62
|
-
action="
|
|
63
|
-
default=
|
|
64
|
+
"--no-stream",
|
|
65
|
+
help="don't stream output of each command",
|
|
66
|
+
action="store_false",
|
|
67
|
+
default=True,
|
|
68
|
+
dest="stream",
|
|
64
69
|
)
|
|
65
70
|
parser.add_argument(
|
|
66
71
|
"-V",
|
|
@@ -16,7 +16,13 @@ from pyallel.errors import InvalidExecutableErrors, InvalidExecutableError
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def indent(output: str) -> str:
|
|
19
|
-
|
|
19
|
+
length = 200
|
|
20
|
+
indented_output = []
|
|
21
|
+
for line in output.splitlines():
|
|
22
|
+
if len(line) > length:
|
|
23
|
+
line = line[:length] + "..."
|
|
24
|
+
indented_output.append(line)
|
|
25
|
+
return "\n".join(" " + line for line in indented_output)
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
def format_time_taken(time_taken: float) -> str:
|
|
@@ -48,6 +54,7 @@ def get_command_status(
|
|
|
48
54
|
icon: str | None = None,
|
|
49
55
|
passed: bool | None = None,
|
|
50
56
|
debug: bool = False,
|
|
57
|
+
timer: bool = False,
|
|
51
58
|
) -> str:
|
|
52
59
|
if passed is True:
|
|
53
60
|
colour = constants.GREEN_BOLD
|
|
@@ -71,7 +78,7 @@ def get_command_status(
|
|
|
71
78
|
|
|
72
79
|
output += f"{constants.NC}]{colour} {msg} "
|
|
73
80
|
|
|
74
|
-
if
|
|
81
|
+
if timer:
|
|
75
82
|
end = process.end
|
|
76
83
|
if not process.end:
|
|
77
84
|
end = time.perf_counter()
|
|
@@ -93,11 +100,11 @@ def run_process(process: Process, debug: bool = False) -> bool:
|
|
|
93
100
|
print(f"{constants.CLEAR_LINE}{constants.CR}", end="")
|
|
94
101
|
|
|
95
102
|
if process.return_code() != 0:
|
|
96
|
-
print(get_command_status(process, passed=False, debug=debug))
|
|
103
|
+
print(get_command_status(process, passed=False, debug=debug, timer=debug))
|
|
97
104
|
print_command_output(process)
|
|
98
105
|
return False
|
|
99
106
|
else:
|
|
100
|
-
print(get_command_status(process, passed=True, debug=debug))
|
|
107
|
+
print(get_command_status(process, passed=True, debug=debug, timer=debug))
|
|
101
108
|
print_command_output(process)
|
|
102
109
|
return True
|
|
103
110
|
|
|
@@ -105,7 +112,6 @@ def run_process(process: Process, debug: bool = False) -> bool:
|
|
|
105
112
|
@dataclass
|
|
106
113
|
class ProcessGroup:
|
|
107
114
|
processes: list[Process]
|
|
108
|
-
fail_fast: bool = False
|
|
109
115
|
interactive: bool = False
|
|
110
116
|
debug: bool = False
|
|
111
117
|
output: dict[UUID, str] = field(default_factory=dict)
|
|
@@ -134,9 +140,9 @@ class ProcessGroup:
|
|
|
134
140
|
continue
|
|
135
141
|
|
|
136
142
|
completed_processes.add(process.id)
|
|
137
|
-
|
|
138
|
-
if
|
|
139
|
-
|
|
143
|
+
process_passed = run_process(process, debug=self.debug)
|
|
144
|
+
if not process_passed:
|
|
145
|
+
passed = False
|
|
140
146
|
|
|
141
147
|
if len(completed_processes) == len(self.processes):
|
|
142
148
|
break
|
|
@@ -160,19 +166,31 @@ class ProcessGroup:
|
|
|
160
166
|
for i, process in enumerate(self.processes, start=1):
|
|
161
167
|
if process.poll() is not None:
|
|
162
168
|
completed_processes.add(process.id)
|
|
169
|
+
if process.return_code() != 0:
|
|
170
|
+
passed = False
|
|
163
171
|
output += get_command_status(
|
|
164
|
-
process,
|
|
172
|
+
process,
|
|
173
|
+
passed=process.return_code() == 0,
|
|
174
|
+
debug=self.debug,
|
|
175
|
+
timer=self.debug,
|
|
165
176
|
)
|
|
166
177
|
output += "\n"
|
|
167
178
|
else:
|
|
168
179
|
output += get_command_status(
|
|
169
|
-
process,
|
|
180
|
+
process,
|
|
181
|
+
icon=constants.ICONS[icon],
|
|
182
|
+
debug=self.debug,
|
|
183
|
+
timer=self.debug,
|
|
170
184
|
)
|
|
171
185
|
output += "\n"
|
|
172
186
|
|
|
173
187
|
process_output = process.read().decode()
|
|
174
188
|
if process_output:
|
|
175
189
|
self.output[process.id] = process_output
|
|
190
|
+
if process.tail_mode.enabled:
|
|
191
|
+
process_output = "\n".join(
|
|
192
|
+
process_output.splitlines()[-process.tail_mode.lines :]
|
|
193
|
+
)
|
|
176
194
|
output += indent(process_output)
|
|
177
195
|
output += "\n"
|
|
178
196
|
if i != len(self.processes):
|
|
@@ -211,7 +229,7 @@ class ProcessGroup:
|
|
|
211
229
|
output = ""
|
|
212
230
|
for process in self.processes:
|
|
213
231
|
if running_process is None and process.id not in completed_processes:
|
|
214
|
-
output += get_command_status(process)
|
|
232
|
+
output += get_command_status(process, debug=self.debug)
|
|
215
233
|
output += "\n"
|
|
216
234
|
running_process = process
|
|
217
235
|
elif running_process is not process:
|
|
@@ -223,6 +241,8 @@ class ProcessGroup:
|
|
|
223
241
|
output += "\n"
|
|
224
242
|
|
|
225
243
|
if process.poll() is not None:
|
|
244
|
+
if process.return_code() != 0:
|
|
245
|
+
passed = False
|
|
226
246
|
process_output_2 = process.readline().decode()
|
|
227
247
|
while process_output_2:
|
|
228
248
|
output += indent(process_output_2)
|
|
@@ -230,7 +250,10 @@ class ProcessGroup:
|
|
|
230
250
|
process_output_2 = process.readline().decode()
|
|
231
251
|
|
|
232
252
|
output += get_command_status(
|
|
233
|
-
process,
|
|
253
|
+
process,
|
|
254
|
+
passed=process.return_code() == 0,
|
|
255
|
+
debug=self.debug,
|
|
256
|
+
timer=self.debug,
|
|
234
257
|
)
|
|
235
258
|
output += "\n\n"
|
|
236
259
|
completed_processes.add(process.id)
|
|
@@ -251,7 +274,6 @@ class ProcessGroup:
|
|
|
251
274
|
cls,
|
|
252
275
|
commands: list[str],
|
|
253
276
|
interactive: bool = False,
|
|
254
|
-
fail_fast: bool = False,
|
|
255
277
|
debug: bool = False,
|
|
256
278
|
) -> ProcessGroup:
|
|
257
279
|
processes: list[Process] = []
|
|
@@ -269,14 +291,19 @@ class ProcessGroup:
|
|
|
269
291
|
return cls(
|
|
270
292
|
processes=processes,
|
|
271
293
|
interactive=interactive,
|
|
272
|
-
fail_fast=fail_fast,
|
|
273
294
|
debug=debug,
|
|
274
295
|
)
|
|
275
296
|
|
|
276
297
|
|
|
298
|
+
@dataclass
|
|
299
|
+
class TailMode:
|
|
300
|
+
enabled: bool = False
|
|
301
|
+
lines: int = 0
|
|
302
|
+
|
|
303
|
+
|
|
277
304
|
@dataclass
|
|
278
305
|
class Process:
|
|
279
|
-
id: UUID
|
|
306
|
+
id: UUID = field(repr=False, compare=False)
|
|
280
307
|
name: str
|
|
281
308
|
args: list[str]
|
|
282
309
|
env: dict[str, str] = field(default_factory=dict)
|
|
@@ -287,6 +314,7 @@ class Process:
|
|
|
287
314
|
fd_name: Path | None = None
|
|
288
315
|
fd_read: BinaryIO | None = None
|
|
289
316
|
fd: int | None = None
|
|
317
|
+
tail_mode: TailMode = field(default_factory=TailMode)
|
|
290
318
|
|
|
291
319
|
def run(self) -> None:
|
|
292
320
|
self.start = time.perf_counter()
|
|
@@ -302,8 +330,6 @@ class Process:
|
|
|
302
330
|
def __del__(self) -> None:
|
|
303
331
|
if self.fd_read:
|
|
304
332
|
self.fd_read.close()
|
|
305
|
-
if self.fd_name:
|
|
306
|
-
self.fd_name.unlink(missing_ok=True)
|
|
307
333
|
|
|
308
334
|
def poll(self) -> int | None:
|
|
309
335
|
if self.process:
|
|
@@ -332,14 +358,19 @@ class Process:
|
|
|
332
358
|
|
|
333
359
|
@classmethod
|
|
334
360
|
def from_command(cls, command: str) -> Process:
|
|
361
|
+
tail_mode = TailMode()
|
|
335
362
|
env = os.environ.copy()
|
|
336
363
|
if " :: " in command:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
364
|
+
modes, _args = command.split(" :: ")
|
|
365
|
+
if modes:
|
|
366
|
+
for mode in modes.split():
|
|
367
|
+
name, value = mode.split("=", maxsplit=1)
|
|
368
|
+
if name == "tail":
|
|
369
|
+
tail_mode.enabled = True
|
|
370
|
+
tail_mode.lines = int(value)
|
|
371
|
+
args = _args.split()
|
|
340
372
|
else:
|
|
341
373
|
args = command.split()
|
|
342
|
-
command_modes = ""
|
|
343
374
|
|
|
344
375
|
parsed_args: list[str] = []
|
|
345
376
|
for arg in args:
|
|
@@ -353,4 +384,6 @@ class Process:
|
|
|
353
384
|
raise InvalidExecutableError(parsed_args[0])
|
|
354
385
|
|
|
355
386
|
str_args = shlex.split(" ".join(parsed_args[1:]))
|
|
356
|
-
return cls(
|
|
387
|
+
return cls(
|
|
388
|
+
id=uuid4(), name=parsed_args[0], args=str_args, env=env, tail_mode=tail_mode
|
|
389
|
+
)
|
pyallel-0.5.0/README.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Pyallel
|
|
2
|
-
|
|
3
|
-
Run and handle the output of multiple executables in `pyallel` (as in parallel)
|
|
4
|
-
|
|
5
|
-
Requires Python >=3.8
|
|
6
|
-
|
|
7
|
-
Tested on Linux and MacOS only
|
|
8
|
-
|
|
9
|
-
# Quick start
|
|
10
|
-
|
|
11
|
-
`pyallel` can be installed using pip:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
pip install pyallel
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Once installed, you can run `pyallel` to see usage information, like so:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
pyallel
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
|
|
24
|
-
|
|
25
|
-
> [!IMPORTANT]
|
|
26
|
-
> If your need to provide arguments to a command, you must surround the command and it's arguments in quotes!
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pyallel "black --color --check --diff ." "MYPY_FORCE_COLOR=1 mypy ." "ruff check --no-fix ."
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## TODOs
|
|
33
|
-
|
|
34
|
-
- [x] Allow output for all provided commands to be streamed to stdout (this will require a
|
|
35
|
-
re-work of how we print command output as we currently just print output once the command
|
|
36
|
-
finishes)
|
|
37
|
-
- [ ] Add CI checks to run the tests and linters against Python versions > 3.8
|
|
38
|
-
- [ ] Allow specific command outputs to be streamed to stdout, while all other
|
|
39
|
-
commands will only get outputted after the streamed commands have completed (such as running
|
|
40
|
-
`pytest` as a streamed command, whilst running `mypy`, `ruff` etc. as non-streamed commands).
|
|
41
|
-
This will require adding special arguments/modes to each command so they are treated
|
|
42
|
-
differently from other commands
|
|
43
|
-
- [ ] Add visual examples of `pyallel` in action
|
|
44
|
-
- [ ] Add custom parsing of command output to support filtering for errors (like vim's
|
|
45
|
-
`errorformat`)
|
|
46
|
-
- [x] Provide a way to set environment variables for each command to run with
|
|
47
|
-
- [ ] Allow list of files to be provided to supply as input arguments to each command
|
|
48
|
-
- [ ] Allow input to be piped into `pyallel` via stdin to supply as standard input to each
|
|
49
|
-
command
|
|
50
|
-
- [ ] Add custom config file for `pyallel` to read from as an alternative to providing
|
|
51
|
-
arguments via the command line
|
|
52
|
-
- [ ] Add support for providing config via a `[tool.pyallel]` section in a
|
|
53
|
-
`pyproject.toml` file in the current working directory
|
|
54
|
-
- [ ] Maybe allow command dependencies to be defined in a python file where commands are
|
|
55
|
-
decorated with info that details it's dependencies?
|
|
56
|
-
- [x] Add test suite
|
|
57
|
-
- [x] Improve error handling when parsing provided commands (check they are valid executables)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|