pyallel 2.0.5__tar.gz → 2.1.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-2.1.0/.vim/project.shada +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/PKG-INFO +4 -3
- {pyallel-2.0.5 → pyallel-2.1.0}/README.md +3 -2
- {pyallel-2.0.5 → pyallel-2.1.0}/benchmark_process.py +3 -10
- pyallel-2.1.0/pyproject.toml +133 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/colours.py +1 -1
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/constants.py +4 -4
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/errors.py +3 -3
- pyallel-2.1.0/src/pyallel/logging.py +14 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/main.py +15 -10
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/parser.py +17 -8
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/printer.py +93 -59
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/process.py +12 -31
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/process_group.py +7 -12
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/process_group_manager.py +13 -13
- pyallel-2.1.0/tests/conftest.py +13 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/test_main.py +39 -173
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/test_printer.py +17 -19
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/test_process.py +6 -39
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/test_process_group.py +12 -19
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/test_process_manager.py +66 -27
- {pyallel-2.0.5 → pyallel-2.1.0}/uv.lock +28 -1
- pyallel-2.0.5/.vim/project.shada +0 -0
- pyallel-2.0.5/pyproject.toml +0 -48
- {pyallel-2.0.5 → pyallel-2.1.0}/.base_branch +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/.github/workflows/build.yml +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/.github/workflows/test.yml +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/.gitignore +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/Dockerfile +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/Dockerfile.alpine +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/LICENSE +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/bin/ldd +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/bin/pyinstaller.sh +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/build.sh +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/build_all.sh +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/__init__.py +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/src/pyallel/py.typed +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/assets/test_handle_multiple_signals.sh +0 -0
- {pyallel-2.0.5 → pyallel-2.1.0}/tests/assets/test_output.sh +0 -0
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyallel
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Run and handle the output of multiple executables in pyallel (as in parallel)
|
|
5
5
|
Project-URL: Homepage, https://github.com/Danthewaann/pyallel
|
|
6
6
|
Project-URL: Repository, https://github.com/Danthewaann/pyallel
|
|
@@ -39,7 +39,7 @@ pipx install pyallel
|
|
|
39
39
|
Once installed, you can run `pyallel` to see usage information, like so:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [commands ...]
|
|
42
|
+
usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [--debug] [commands ...]
|
|
43
43
|
|
|
44
44
|
run and handle the output of multiple executables in pyallel (as in parallel)
|
|
45
45
|
|
|
@@ -98,6 +98,7 @@ options:
|
|
|
98
98
|
-V, --version print version and exit
|
|
99
99
|
--colour {yes,no,auto}
|
|
100
100
|
colour terminal output, defaults to "auto"
|
|
101
|
+
--debug enable debug mode, which logs debug info to a "pyallel.log" file in the current directory
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
|
|
@@ -158,7 +159,7 @@ pipx install uv && \
|
|
|
158
159
|
before a given command can start)
|
|
159
160
|
- [x] Add support to state how many lines a command can use for it's output in interactive mode
|
|
160
161
|
- [x] Improve printing of output performance by only printing lines that have changed
|
|
161
|
-
- [
|
|
162
|
+
- [x] Add a debug mode that logs debug information to a log file
|
|
162
163
|
- [ ] Maybe add support to allow the user to provide stdin for commands that request it
|
|
163
164
|
(such as a REPL)
|
|
164
165
|
- [ ] Add custom parsing of command output to support filtering for errors (like vim's
|
|
@@ -21,7 +21,7 @@ pipx install pyallel
|
|
|
21
21
|
Once installed, you can run `pyallel` to see usage information, like so:
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [commands ...]
|
|
24
|
+
usage: pyallel [-h] [-t] [-n] [-V] [--colour {yes,no,auto}] [--debug] [commands ...]
|
|
25
25
|
|
|
26
26
|
run and handle the output of multiple executables in pyallel (as in parallel)
|
|
27
27
|
|
|
@@ -80,6 +80,7 @@ options:
|
|
|
80
80
|
-V, --version print version and exit
|
|
81
81
|
--colour {yes,no,auto}
|
|
82
82
|
colour terminal output, defaults to "auto"
|
|
83
|
+
--debug enable debug mode, which logs debug info to a "pyallel.log" file in the current directory
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
Currently you can provide a variable number of `commands` to run to `pyallel`, like so:
|
|
@@ -140,7 +141,7 @@ pipx install uv && \
|
|
|
140
141
|
before a given command can start)
|
|
141
142
|
- [x] Add support to state how many lines a command can use for it's output in interactive mode
|
|
142
143
|
- [x] Improve printing of output performance by only printing lines that have changed
|
|
143
|
-
- [
|
|
144
|
+
- [x] Add a debug mode that logs debug information to a log file
|
|
144
145
|
- [ ] Maybe add support to allow the user to provide stdin for commands that request it
|
|
145
146
|
(such as a REPL)
|
|
146
147
|
- [ ] Add custom parsing of command output to support filtering for errors (like vim's
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Ad-hoc benchmark comparing Process's stdout reading approach against two
|
|
3
|
-
scenarios:
|
|
1
|
+
"""Ad-hoc benchmark comparing Process's stdout reading approach against two scenarios.
|
|
4
2
|
|
|
5
3
|
1. A chatty command that produces a lot of output while it's running.
|
|
6
4
|
2. A quick command that exits almost immediately, followed by a period of
|
|
@@ -26,9 +24,7 @@ def cpu_time() -> float:
|
|
|
26
24
|
return usage.ru_utime + usage.ru_stime
|
|
27
25
|
|
|
28
26
|
|
|
29
|
-
def run_scenario(
|
|
30
|
-
name: str, command: str, poll_seconds: float, extra_idle_seconds: float
|
|
31
|
-
) -> None:
|
|
27
|
+
def run_scenario(name: str, command: str, poll_seconds: float, extra_idle_seconds: float) -> None:
|
|
32
28
|
process = Process(1, command)
|
|
33
29
|
|
|
34
30
|
start_wall = time.perf_counter()
|
|
@@ -49,10 +45,7 @@ def run_scenario(
|
|
|
49
45
|
end_wall = time.perf_counter()
|
|
50
46
|
end_cpu = cpu_time()
|
|
51
47
|
|
|
52
|
-
print(
|
|
53
|
-
f"{name:12} wall={end_wall - start_wall:6.3f}s "
|
|
54
|
-
f"cpu={end_cpu - start_cpu:6.3f}s bytes={total_bytes}"
|
|
55
|
-
)
|
|
48
|
+
print(f"{name:12} wall={end_wall - start_wall:6.3f}s cpu={end_cpu - start_cpu:6.3f}s bytes={total_bytes}")
|
|
56
49
|
|
|
57
50
|
|
|
58
51
|
def main() -> None:
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyallel"
|
|
3
|
+
version = "2.1.0"
|
|
4
|
+
description = "Run and handle the output of multiple executables in pyallel (as in parallel)"
|
|
5
|
+
authors = [{ name = "Daniel Black", email = "danielcrblack@gmail.com" }]
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
keywords = [
|
|
10
|
+
"parallel",
|
|
11
|
+
"command",
|
|
12
|
+
"runner",
|
|
13
|
+
"executable",
|
|
14
|
+
"shell",
|
|
15
|
+
"terminal",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: MacOS :: MacOS X",
|
|
22
|
+
"Operating System :: POSIX :: Linux",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/Danthewaann/pyallel"
|
|
27
|
+
Repository = "https://github.com/Danthewaann/pyallel"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
pyallel = "pyallel.main:entry_point"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.4.3,<8",
|
|
35
|
+
"mypy>=1.7.0,<2",
|
|
36
|
+
"pyinstaller==6.11.0",
|
|
37
|
+
"ruff>=0.15.20",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["hatchling"]
|
|
42
|
+
build-backend = "hatchling.build"
|
|
43
|
+
|
|
44
|
+
[tool.mypy]
|
|
45
|
+
strict = true
|
|
46
|
+
show_error_codes = true
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
addopts = "-vvv"
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
# Allow lines to be as long as 120 columns
|
|
53
|
+
line-length = 120
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
# Ruff linting rules are listed within: https://beta.ruff.rs/docs/rules
|
|
57
|
+
#
|
|
58
|
+
# Globally enable all rules by default. We ignore ones we don't want below
|
|
59
|
+
select = [
|
|
60
|
+
"ALL"
|
|
61
|
+
]
|
|
62
|
+
# Rules to ignore globally
|
|
63
|
+
ignore = [
|
|
64
|
+
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
|
|
65
|
+
"BLE001", # Do not catch blind exceptions
|
|
66
|
+
"B904", # Within an except* clause, raise exceptions with raise ... from err or raise ... from None
|
|
67
|
+
"COM812", # Trailing comma missing
|
|
68
|
+
"COM819", # Trailing comma prohibited
|
|
69
|
+
"D1", # pydocstyle: undocumented code
|
|
70
|
+
"D203", # pydocstyle: 1 blank line required before class docstring
|
|
71
|
+
"D213", # pydocstyle: Multi-line docstring summary should start at the second line
|
|
72
|
+
"D401", # pydocstyle: First line of docstring should be in imperative mood
|
|
73
|
+
"D413", # pydocstyle: Missing blank line after last section
|
|
74
|
+
"E501", # Line too long
|
|
75
|
+
"EM", # flake8-errmsg: https://docs.astral.sh/ruff/rules/#flake8-errmsg-em
|
|
76
|
+
"FIX002", # Line contains TODO, consider resolving the issue
|
|
77
|
+
"PLR0912", # Too many branches
|
|
78
|
+
"S106", # Possible hardcoded password assigned to argument
|
|
79
|
+
"S603", # subprocess call: check for execution of untrusted input
|
|
80
|
+
"S604", # Function call with shell=True parameter identified, security issue
|
|
81
|
+
"T20", # flake8-print: https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
|
82
|
+
"TD002", # Missing author in TODO
|
|
83
|
+
"TRY003", # Avoid specifying long messages outside the exception class
|
|
84
|
+
"TRY201", # Use raise without specifying exception name
|
|
85
|
+
"TRY400", # Use logging.exception instead of logging.error
|
|
86
|
+
"PLR0913", # Too many arguments in function definition (> 5)
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
# Rules to ignore on a per file basis
|
|
90
|
+
[tool.ruff.lint.per-file-ignores]
|
|
91
|
+
"**/{tests}/*" = [
|
|
92
|
+
"D", # ignore pydocstyle docstring rules in tests
|
|
93
|
+
"FBT", # flake8-boolean-trap: https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
|
|
94
|
+
"INP001", # File is part of an implicit namespace package. Add an __init__.py
|
|
95
|
+
"N801", # Class name should use CapWords convention
|
|
96
|
+
"PLR0913", # Too many arguments in function definition
|
|
97
|
+
"PLR2004", # Magic value used in comparison, consider replacing with a constant variable
|
|
98
|
+
"PT019", # Fixture without value is injected as parameter, use @pytest.mark.usefixtures instead
|
|
99
|
+
"S101", # Use of assert detected
|
|
100
|
+
"S104", # Possible binding to all interfaces
|
|
101
|
+
"SLF001", # Private member accessed
|
|
102
|
+
"S108", # Probable insecure usage of temporary file or directory
|
|
103
|
+
"S607", # Starting a process with a partial executable path
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
# Linter plugin configurations
|
|
107
|
+
#
|
|
108
|
+
# From: https://docs.astral.sh/ruff/settings
|
|
109
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
110
|
+
# Disallow all relative imports.
|
|
111
|
+
ban-relative-imports = "all"
|
|
112
|
+
|
|
113
|
+
[tool.ruff.lint.isort]
|
|
114
|
+
combine-as-imports = true
|
|
115
|
+
force-wrap-aliases = true
|
|
116
|
+
known-first-party = ["pizzabox", "tests"]
|
|
117
|
+
|
|
118
|
+
[tool.ruff.lint.mccabe]
|
|
119
|
+
# The maximum function complexity to allow before triggering C901 errors
|
|
120
|
+
max-complexity = 20
|
|
121
|
+
|
|
122
|
+
# Configure the ruff formatter
|
|
123
|
+
[tool.ruff.format]
|
|
124
|
+
# Like Black, use double quotes for strings
|
|
125
|
+
quote-style = "double"
|
|
126
|
+
# Like Black, indent with spaces, rather than tabs
|
|
127
|
+
indent-style = "space"
|
|
128
|
+
# Like Black, respect magic trailing commas
|
|
129
|
+
skip-magic-trailing-comma = false
|
|
130
|
+
# Like Black, automatically detect the appropriate line ending
|
|
131
|
+
line-ending = "auto"
|
|
132
|
+
# Enable reformatting of code snippets in docstrings
|
|
133
|
+
docstring-code-format = true
|
|
@@ -21,7 +21,7 @@ class Colours:
|
|
|
21
21
|
def from_colour(cls, colour: Literal["yes", "no", "auto"]) -> Colours:
|
|
22
22
|
colours = cls()
|
|
23
23
|
|
|
24
|
-
if colour == "no" or colour == "auto" and not constants.IN_TTY:
|
|
24
|
+
if colour == "no" or (colour == "auto" and not constants.IN_TTY):
|
|
25
25
|
for field in fields(colours):
|
|
26
26
|
setattr(colours, field.name, "")
|
|
27
27
|
|
|
@@ -12,18 +12,18 @@ ANSI_ESCAPE = re.compile(r"(\x9B|\x1B\[|\x1B\()[0-?]*[ -\/]*[@-~]")
|
|
|
12
12
|
|
|
13
13
|
if IN_TTY:
|
|
14
14
|
|
|
15
|
-
def
|
|
15
|
+
def columns() -> int:
|
|
16
16
|
return shutil.get_terminal_size().columns
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def lines() -> int:
|
|
19
19
|
return shutil.get_terminal_size().lines
|
|
20
20
|
|
|
21
21
|
else:
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def columns() -> int:
|
|
24
24
|
return sys.maxsize
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def lines() -> int:
|
|
27
27
|
return sys.maxsize
|
|
28
28
|
|
|
29
29
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
class PyallelError(Exception):
|
|
2
|
-
"""Base error for issues raised by pyallel"""
|
|
2
|
+
"""Base error for issues raised by pyallel."""
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class InvalidLinesModifierError(PyallelError):
|
|
6
|
-
"""Raised when the lines modifier is invalid"""
|
|
6
|
+
"""Raised when the lines modifier is invalid."""
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class NoCommandsForProcessGroupError(PyallelError):
|
|
10
|
-
"""Raised when no commands have been provided for a process group"""
|
|
10
|
+
"""Raised when no commands have been provided for a process group."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def configure_logging(*, debug: bool = False) -> None:
|
|
6
|
+
if debug:
|
|
7
|
+
logging.basicConfig(
|
|
8
|
+
filename="pyallel.log",
|
|
9
|
+
format="%(asctime)s:%(name)s:%(lineno)d:%(message)s",
|
|
10
|
+
datefmt="%Y-%m-%dT%H:%M:%S",
|
|
11
|
+
level=logging.DEBUG,
|
|
12
|
+
)
|
|
13
|
+
else:
|
|
14
|
+
logging.basicConfig(handlers=[logging.StreamHandler(stream=open(os.devnull, "w"))]) # noqa: PTH123, SIM115
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import importlib.metadata
|
|
4
|
+
import logging
|
|
4
5
|
import sys
|
|
5
6
|
import time
|
|
6
7
|
import traceback
|
|
@@ -8,6 +9,7 @@ import traceback
|
|
|
8
9
|
from pyallel import constants
|
|
9
10
|
from pyallel.colours import Colours
|
|
10
11
|
from pyallel.errors import PyallelError
|
|
12
|
+
from pyallel.logging import configure_logging
|
|
11
13
|
from pyallel.parser import Arguments, create_parser
|
|
12
14
|
from pyallel.printer import (
|
|
13
15
|
InteractiveConsolePrinter,
|
|
@@ -16,6 +18,8 @@ from pyallel.printer import (
|
|
|
16
18
|
)
|
|
17
19
|
from pyallel.process_group_manager import ProcessGroupManager
|
|
18
20
|
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
def entry_point(*args: str) -> int:
|
|
21
25
|
args = args or tuple(sys.argv[1:])
|
|
@@ -31,6 +35,8 @@ def entry_point(*args: str) -> int:
|
|
|
31
35
|
parser.print_help()
|
|
32
36
|
return 2
|
|
33
37
|
|
|
38
|
+
configure_logging(debug=parsed_args.debug)
|
|
39
|
+
|
|
34
40
|
colours = Colours.from_colour(parsed_args.colour)
|
|
35
41
|
printer: Printer
|
|
36
42
|
if not parsed_args.interactive or not constants.IN_TTY:
|
|
@@ -41,28 +47,27 @@ def entry_point(*args: str) -> int:
|
|
|
41
47
|
try:
|
|
42
48
|
process_group_manager = ProcessGroupManager.from_args(*parsed_args.commands)
|
|
43
49
|
except PyallelError as e:
|
|
44
|
-
print(f"{colours.red_bold}Error: {
|
|
50
|
+
print(f"{colours.red_bold}Error: {e!s}{colours.reset_colour}")
|
|
45
51
|
return 1
|
|
46
52
|
|
|
53
|
+
logger.debug("starting run with arguments:\n%s", parsed_args)
|
|
47
54
|
try:
|
|
48
55
|
exit_code = run(process_group_manager, printer)
|
|
49
56
|
except Exception:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
57
|
+
logger.exception("failed run with arguments:\n%s", parsed_args)
|
|
58
|
+
print(f"{colours.red_bold}Error: {traceback.format_exc()}{colours.reset_colour}")
|
|
53
59
|
return 1
|
|
54
60
|
|
|
55
61
|
if exit_code == 1:
|
|
62
|
+
logger.error("failed run with arguments:\n%s", parsed_args)
|
|
56
63
|
process_group = process_group_manager.get_cur_process_group_output()
|
|
57
|
-
print(
|
|
58
|
-
f"\n{colours.red_bold}ERROR: the following commands failed{colours.reset_colour}"
|
|
59
|
-
)
|
|
64
|
+
print(f"\n{colours.red_bold}ERROR: the following commands failed{colours.reset_colour}")
|
|
60
65
|
for process_output in process_group.processes:
|
|
61
66
|
process_poll = process_output.process.poll()
|
|
62
67
|
if process_poll and process_poll > 0:
|
|
63
|
-
print(
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
print(f" {colours.red_bold}{process_output.process.command}{colours.reset_colour}")
|
|
69
|
+
else:
|
|
70
|
+
logger.debug("finished run with arguments:\n%s", parsed_args)
|
|
66
71
|
|
|
67
72
|
return exit_code
|
|
68
73
|
|
|
@@ -10,13 +10,16 @@ class Arguments:
|
|
|
10
10
|
interactive: bool
|
|
11
11
|
timer: bool
|
|
12
12
|
version: bool
|
|
13
|
+
debug: bool
|
|
13
14
|
|
|
14
15
|
def __repr__(self) -> str:
|
|
15
|
-
msg = "
|
|
16
|
+
msg = ""
|
|
16
17
|
padding = len(sorted(self.__dict__.keys(), key=len, reverse=True)[0]) + 1
|
|
17
18
|
for field, value in self.__dict__.items():
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
if field == "commands":
|
|
20
|
+
value = " ".join(value) # noqa: PLW2901
|
|
21
|
+
msg += f"{field: <{padding}}: {value}\n"
|
|
22
|
+
return msg.strip()
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
DESCRIPTION = r"""run and handle the output of multiple executables in %(prog)s (as in parallel)
|
|
@@ -24,11 +27,11 @@ DESCRIPTION = r"""run and handle the output of multiple executables in %(prog)s
|
|
|
24
27
|
RUNNING COMMANDS
|
|
25
28
|
================
|
|
26
29
|
to run multiple commands you must separate them using the command separator symbol (::)
|
|
27
|
-
|
|
30
|
+
|
|
28
31
|
%(prog)s mypy . :: black .
|
|
29
32
|
|
|
30
|
-
if you want to provide options to a command you need to use the double dash symbol (--) to indicate that
|
|
31
|
-
any options provided after this symbol should not be interpreted by %(prog)s
|
|
33
|
+
if you want to provide options to a command you need to use the double dash symbol (--) to indicate that
|
|
34
|
+
any options provided after this symbol should not be interpreted by %(prog)s
|
|
32
35
|
|
|
33
36
|
%(prog)s -n -- mypy -V :: black --version
|
|
34
37
|
|
|
@@ -45,7 +48,7 @@ modifiers can also be set for commands to augment their behaviour using the comm
|
|
|
45
48
|
|
|
46
49
|
lines (only used in interactive mode):
|
|
47
50
|
the lines modifier allows you to specify how many lines the command output can take up on the screen
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
%(prog)s lines=90 :::: echo running long command... :: echo running other command...
|
|
50
53
|
|
|
51
54
|
90 is expressed as a percentage value, which must be between 1 and 100 inclusive
|
|
@@ -54,7 +57,7 @@ SHELL SYNTAX
|
|
|
54
57
|
============
|
|
55
58
|
each command is executed inside its own shell, this means shell syntax is supported.
|
|
56
59
|
it is important to note that certain shell syntax must be escaped using backslashes (\)
|
|
57
|
-
or wrapped in single quotes (''), otherwise it will be evaluated in your current
|
|
60
|
+
or wrapped in single quotes (''), otherwise it will be evaluated in your current
|
|
58
61
|
shell immediately instead of the shell that your command will run within.
|
|
59
62
|
|
|
60
63
|
some examples of using shell syntax are below (single quotes are used only if required)
|
|
@@ -108,5 +111,11 @@ def create_parser() -> ArgumentParser:
|
|
|
108
111
|
choices=("yes", "no", "auto"),
|
|
109
112
|
default="auto",
|
|
110
113
|
)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--debug",
|
|
116
|
+
help='enable debug mode, which logs debug info to a "pyallel.log" file in the current directory',
|
|
117
|
+
action="store_true",
|
|
118
|
+
default=False,
|
|
119
|
+
)
|
|
111
120
|
|
|
112
121
|
return parser
|