ae-shell 0.3.15__tar.gz → 0.3.17__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.
- {ae_shell-0.3.15 → ae_shell-0.3.17}/LICENSE.md +1 -1
- {ae_shell-0.3.15/ae_shell.egg-info → ae_shell-0.3.17}/PKG-INFO +6 -6
- {ae_shell-0.3.15 → ae_shell-0.3.17}/README.md +5 -5
- ae_shell-0.3.17/ae/shell.py +323 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17/ae_shell.egg-info}/PKG-INFO +6 -6
- {ae_shell-0.3.15 → ae_shell-0.3.17}/pyproject.toml +4 -1
- {ae_shell-0.3.15 → ae_shell-0.3.17}/setup.py +6 -7
- ae_shell-0.3.17/tests/test_shell.py +596 -0
- ae_shell-0.3.15/ae/shell.py +0 -257
- ae_shell-0.3.15/tests/test_shell.py +0 -362
- {ae_shell-0.3.15 → ae_shell-0.3.17}/ae_shell.egg-info/SOURCES.txt +0 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17}/ae_shell.egg-info/dependency_links.txt +0 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17}/ae_shell.egg-info/requires.txt +0 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17}/ae_shell.egg-info/top_level.txt +0 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17}/ae_shell.egg-info/zip-safe +0 -0
- {ae_shell-0.3.15 → ae_shell-0.3.17}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: ae namespace module portion shell: shell execution and environment helpers
|
|
5
5
|
Home-page: https://gitlab.com/ae-group/ae_shell
|
|
6
6
|
Author: AndiEcker
|
|
@@ -62,15 +62,15 @@ Dynamic: requires-dist
|
|
|
62
62
|
Dynamic: requires-python
|
|
63
63
|
Dynamic: summary
|
|
64
64
|
|
|
65
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
66
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
67
|
-
# shell 0.3.
|
|
65
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
66
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
67
|
+
# shell 0.3.17
|
|
68
68
|
|
|
69
69
|
[](
|
|
70
70
|
https://gitlab.com/ae-group/ae_shell)
|
|
71
71
|
[](
|
|
73
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
|
|
74
74
|
[](
|
|
75
75
|
https://pypi.org/project/ae-shell/#history)
|
|
76
76
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
2
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
3
|
-
# shell 0.3.
|
|
1
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
2
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
3
|
+
# shell 0.3.17
|
|
4
4
|
|
|
5
5
|
[](
|
|
6
6
|
https://gitlab.com/ae-group/ae_shell)
|
|
7
7
|
[](
|
|
9
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
|
|
10
10
|
[](
|
|
11
11
|
https://pypi.org/project/ae-shell/#history)
|
|
12
12
|
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""
|
|
2
|
+
shell execution and environment helpers
|
|
3
|
+
=======================================
|
|
4
|
+
|
|
5
|
+
this module is designed to provide a comprehensive set of constants and helper functions
|
|
6
|
+
to manage shell printouts, OS environment variables and to execute shell commands.
|
|
7
|
+
|
|
8
|
+
* :func:`debug_or_verbose`: checks if the application is running in debug or verbose mode.
|
|
9
|
+
* :func:`get_domain_user_var`: retrieves an OS environment variable value for a specific domain and/or user.
|
|
10
|
+
* :func:`hint`: provides a hint message for console printouts based on the provided arguments.
|
|
11
|
+
* :func:`in_os_env`: context manager to temporarily add environment variables from the ``.env`` files onto `os.environ`.
|
|
12
|
+
* :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs and printouts.
|
|
13
|
+
* :func:`output_line_split`: decode and split the specified shell/console output streams into line chunks.
|
|
14
|
+
* :func:`output_zero_split`: decode and split the specified shell/console output streams separated by `NUL` (\\0) chars.
|
|
15
|
+
* :func:`sh_exec`: execute command in the current working directory of the OS console/shell.
|
|
16
|
+
* :func:`sh_exit_if_exec_err`: extended version of :func:`sh_exec` with automatically checks for errors
|
|
17
|
+
after a command is executed and handles application shutdown/termination gracefully.
|
|
18
|
+
|
|
19
|
+
* :data:`STDERR_BEG_MARKER`: marker used in the console/shell printouts for the beginning of merged-in stderr output.
|
|
20
|
+
* :data:`STDERR_END_MARKER`: marker used in the console/shell printouts for the end of merged-in stderr output.
|
|
21
|
+
"""
|
|
22
|
+
import os
|
|
23
|
+
import shlex
|
|
24
|
+
import subprocess
|
|
25
|
+
|
|
26
|
+
from collections.abc import Callable, Iterable, Iterator, MutableMapping
|
|
27
|
+
from contextlib import contextmanager
|
|
28
|
+
from typing import Any, cast, overload
|
|
29
|
+
|
|
30
|
+
from ae.base import UNSET, UnsetType, dummy_function, env_str, norm_name # type: ignore
|
|
31
|
+
from ae.system import load_env_var_defaults, os_env_venv # type: ignore
|
|
32
|
+
from ae.core import main_app_instance, AppBase # type: ignore
|
|
33
|
+
from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__version__ = '0.3.17'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.sh_exec.output_lines` begin stderr lines marker
|
|
40
|
+
STDERR_END_MARKER = '^^^ STDERR ^^^' #: end stderr lines marker in :paramref:`ae.shell.sh_exec.output_lines`
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def debug_or_verbose(app_obj: ConsoleApp | UnsetType | None = None) -> bool:
|
|
44
|
+
""" determine if the current app runs in debug|verbose mode, while preventing early .get_option() call an app init.
|
|
45
|
+
|
|
46
|
+
:param app_obj: optional ConsoleApp instance (def=main_app_instance()).
|
|
47
|
+
:return: a boolean False when the main app debug level is :data:`~ae.core.DEBUG_LEVEL_DISABLED`
|
|
48
|
+
and the app option --more_verbose is not specified (in cfg-file or at the command line),
|
|
49
|
+
else True.
|
|
50
|
+
|
|
51
|
+
.. note:: the return value on app startup/initialization, before the command line parsing, is always True.
|
|
52
|
+
|
|
53
|
+
.. hint::
|
|
54
|
+
the debug mode can be activated via the :class:`~ae.console.ConsoleApp` option `debug_level`, specified either
|
|
55
|
+
in a config file or via the command line options. the verbose mode get activated via the `more_verbose` option.
|
|
56
|
+
"""
|
|
57
|
+
if app_obj is None:
|
|
58
|
+
app_obj = main_app_instance()
|
|
59
|
+
|
|
60
|
+
return bool(
|
|
61
|
+
not isinstance(app_obj, AppBase) # prevent exception in early app startup and unit test runs
|
|
62
|
+
or app_obj.debug # main_app.debug_level > DEBUG_LEVEL_DISABLED
|
|
63
|
+
or not isinstance(app_obj, ConsoleApp) # return True for pure ae.core.AppBase instances
|
|
64
|
+
or not getattr(app_obj, '_parsed_arguments', None) # ConsoleApp._parsed_arguments args Namespace not created
|
|
65
|
+
or app_obj.get_option('more_verbose')) # optional ConsoleApp instance verbose option
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_domain_user_var(variable_name: str, domain: str = "", user: str = "") -> Any:
|
|
69
|
+
""" determine the value of an OS environment variable for a specific domain and/or username.
|
|
70
|
+
|
|
71
|
+
:param variable_name: name of the config variable.
|
|
72
|
+
:param domain: name of the domain.
|
|
73
|
+
:param user: name/id of the user to get a user-specific value of.
|
|
74
|
+
:return: domain/user-specific value of the specified config variable.
|
|
75
|
+
"""
|
|
76
|
+
parts = (MAIN_SECTION_NAME, variable_name.lower(), f'AT_{norm_name(domain)}'.lower(), norm_name(user).lower())
|
|
77
|
+
value = None
|
|
78
|
+
if domain:
|
|
79
|
+
if user:
|
|
80
|
+
value = env_str('_'.join(parts), convert_name=True)
|
|
81
|
+
if value is None:
|
|
82
|
+
value = env_str('_'.join(parts[:-1]), convert_name=True)
|
|
83
|
+
elif user:
|
|
84
|
+
value = env_str('_'.join(parts[:2] + parts[-1:]), convert_name=True)
|
|
85
|
+
|
|
86
|
+
if value is None:
|
|
87
|
+
value = env_str('_'.join(parts[:2]), convert_name=True)
|
|
88
|
+
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def hint(command: str, action: Callable | str, message_suffix: str = "") -> str:
|
|
93
|
+
""" return hint string in debug/verbose mode, to be appended onto a shell/console output.
|
|
94
|
+
|
|
95
|
+
:param command: shell command.
|
|
96
|
+
:param action: shell command action function/method.
|
|
97
|
+
:param message_suffix: extra message text, added to the end of the returned console output string.
|
|
98
|
+
:return: in debug/verbose mode return a string with leading line feed to be sent
|
|
99
|
+
to console output, else return an empty string.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(action, str):
|
|
102
|
+
action = action.__name__
|
|
103
|
+
return f"{os.linesep} (run: {command} {action}{message_suffix})" if debug_or_verbose() else ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@contextmanager
|
|
107
|
+
def in_os_env(start_dir: str = "") -> Iterator[MutableMapping[str, str]]:
|
|
108
|
+
""" temporarily add environment variables from the ``.env`` files that not exist in :attr:`os.environ` to it.
|
|
109
|
+
|
|
110
|
+
:param start_dir: path to the folder where the first dotenv file (with the highest priority) is stored.
|
|
111
|
+
:return: yielding the OS env variables that got added to os.environ in this temporary context.
|
|
112
|
+
"""
|
|
113
|
+
loaded_env_vars = load_env_var_defaults(start_dir, os.environ)
|
|
114
|
+
try:
|
|
115
|
+
yield loaded_env_vars
|
|
116
|
+
finally:
|
|
117
|
+
for var_name in loaded_env_vars:
|
|
118
|
+
os.environ.pop(var_name)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@overload
|
|
122
|
+
def mask_token(text: str) -> str: ...
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@overload
|
|
126
|
+
def mask_token(text: list[str]) -> list[str]: ...
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@overload
|
|
130
|
+
def mask_token(text: str | list[str]) -> str | list[str]: ...
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def mask_token(text: str | list[str]) -> str | list[str]:
|
|
134
|
+
""" hide most parts of any Codeberg/GitHub/GitHub URL tokens found in the specified text/-lines.
|
|
135
|
+
|
|
136
|
+
:param text: text, specified either as str object or as a list of str objects (lines),
|
|
137
|
+
each str/line get searched for URL tokens, to hide/mask the most part of them.
|
|
138
|
+
:return: text with masked URL tokens (only leaving the first/last 3 token characters unmasked).
|
|
139
|
+
|
|
140
|
+
.. note:: see also :func:`ae.base.mask_url` of a more generic way to hide passwords and tokens in URLs.
|
|
141
|
+
"""
|
|
142
|
+
if is_str_arg := isinstance(text, str):
|
|
143
|
+
lines = [text]
|
|
144
|
+
else:
|
|
145
|
+
lines = list(text) # copy to not change text list content
|
|
146
|
+
|
|
147
|
+
url_beg = 'https://' # PDV_REPO_HOST_PROTOCOL
|
|
148
|
+
url_beg_len = len(url_beg)
|
|
149
|
+
for tok_beg, tok_end in ((':', '@codeberg.org'), ('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
|
|
150
|
+
for idx, line in enumerate(lines):
|
|
151
|
+
beg = -1
|
|
152
|
+
while ((beg := line.find(url_beg, beg + 1)) != -1 and
|
|
153
|
+
(beg := line.find(tok_beg, beg + url_beg_len)) != -1 and
|
|
154
|
+
(end := line.find(tok_end, beg)) != -1):
|
|
155
|
+
line = line[:beg + 3] + "***-masked-token-***" + line[end - 3:]
|
|
156
|
+
lines[idx] = line
|
|
157
|
+
|
|
158
|
+
return lines[0] if is_str_arg else lines
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def output_line_split(output: bytes) -> list[str]:
|
|
162
|
+
""" decode and split the specified shell/console output streams into line chunks.
|
|
163
|
+
|
|
164
|
+
:param output: captured stdout/stderr output from an executed OS shell command.
|
|
165
|
+
:return: list of non-empty shell/console output lines, decoded into string.
|
|
166
|
+
"""
|
|
167
|
+
return [line for line in output.decode().splitlines() if line.strip()]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def output_zero_split(output: bytes) -> list[str]:
|
|
171
|
+
""" decode and split the specified shell/console output streams separated by `NUL` (\\0) characters.
|
|
172
|
+
|
|
173
|
+
:param output: captured stdout/stderr output from an executed OS shell command (e.g. `env -0`).
|
|
174
|
+
:return: list of non-empty shell/console output chunks, decoded into string.
|
|
175
|
+
"""
|
|
176
|
+
return [line for line in output.decode().split('\0') if line.strip()]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
180
|
+
def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
|
|
181
|
+
output_lines: list[str] | None = None, app_obj: AppBase | UnsetType | None = None, shell: bool = False,
|
|
182
|
+
env_vars: dict[str, str] | UnsetType | None = UNSET, err_redirect: int | None = None,
|
|
183
|
+
decoder_splitter: Callable[[bytes], list[str]] = output_line_split) -> int:
|
|
184
|
+
""" execute command in the current working directory of the OS console/shell.
|
|
185
|
+
|
|
186
|
+
:param command_line: command line string to execute on the console/shell. could contain command line args
|
|
187
|
+
separated by whitespace characters (alternatively use :paramref:`.extra_args`).
|
|
188
|
+
:param extra_args: optional iterable with extra command line arguments.
|
|
189
|
+
:param console_input: optional string to be sent to the stdin stream of the console/shell.
|
|
190
|
+
:param output_lines: specify a list to be extended with the lines printed on the console/shell stdout stream,
|
|
191
|
+
and to also hide this output on the console. if and how the stderr stream get also
|
|
192
|
+
hidden/redirected to this list can be controlled via the value passed in the argument
|
|
193
|
+
:paramref:`.err_redirect`.
|
|
194
|
+
:param app_obj: optional :class:`~ae.core.AppBase`/:class:`~ae.console.ConsoleApp` instance, used for
|
|
195
|
+
logging. if not specified or None and if :func:`~ae.core.main_app_instance()` returns
|
|
196
|
+
None then the Python :func:`print` function is used.
|
|
197
|
+
specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
|
|
198
|
+
:param shell: pass True to execute command in the default OS shell (for more info check the
|
|
199
|
+
documentation of the parameter :paramref:`~subprocess.run.shell` of the
|
|
200
|
+
:func:`subprocess.run` function).
|
|
201
|
+
:param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
|
|
202
|
+
if not specified or `UNSET` then an isolated dict copy of :attr:`os.environ` will get
|
|
203
|
+
passed onto :func:`subprocess.run` in order to avoid potential runtime errors, if the
|
|
204
|
+
parent process (or a concurrent thread) modifies :attr:`os.environ` during the creation
|
|
205
|
+
of the subprocess. Additionally, with the convertion of the special :class:`_Environ`
|
|
206
|
+
object into a standard dict, the execution will result slightly faster and avoids
|
|
207
|
+
any special method overrides interfering with the child process creation. it also is
|
|
208
|
+
preventing rare errors with multithread-processes that try to change OS env variable
|
|
209
|
+
(e.g. Conda in relation with pip could lead to raise a `RuntimeError: dictionary
|
|
210
|
+
changed size during iteration` exception).
|
|
211
|
+
specify `None` in order to use the original/unisolated :attr:`os.environ` object.
|
|
212
|
+
:param err_redirect: this argument controls if and how the output of the executed command onto the console
|
|
213
|
+
stderr stream gets captured/redirected. it gets passed directly onto the
|
|
214
|
+
:paramref:`~subprocess.run.stderr` argument of :func:`subprocess.run` function.
|
|
215
|
+
if the argument of :paramref:`.output_lines` is a list, and you specified the
|
|
216
|
+
argument value :data:`subprocess.PIPE`, then the stderr output will get added at the
|
|
217
|
+
end of this list (enclosed between the list items :data:`STDERR_BEG_MARKER` and
|
|
218
|
+
:data:`STDERR_END_MARKER`). if the argument of :paramref:`.output_lines` is a
|
|
219
|
+
list, and you specified the argument value :data:`subprocess.STDOUT` then the stderr
|
|
220
|
+
output will get merged without any markers into this list in the order they get printed.
|
|
221
|
+
specify :data:`subprocess.DEVNULL` to hide any stderr output onto the console/shell
|
|
222
|
+
as well as in the :paramref:`.output_lines` list. if you specify `None`
|
|
223
|
+
(the default argument) then the stderr output will be printed only on the console.
|
|
224
|
+
:param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
|
|
225
|
+
of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
|
|
226
|
+
:return: return code of the executed command or 126 if execution raised any other exception.
|
|
227
|
+
"""
|
|
228
|
+
if shell:
|
|
229
|
+
all_args: str | list[str] = command_line + (" " + " ".join(extra_args) if extra_args else "")
|
|
230
|
+
else:
|
|
231
|
+
all_args = shlex.split(command_line) + list(extra_args)
|
|
232
|
+
masked_args = mask_token(all_args)
|
|
233
|
+
if app_obj is None:
|
|
234
|
+
app_obj = main_app_instance()
|
|
235
|
+
print_out = dummy_function if app_obj is UNSET else app_obj.print_out if isinstance(app_obj, AppBase) else print
|
|
236
|
+
debug_out = dummy_function if app_obj is UNSET else app_obj.debug_out if isinstance(app_obj, AppBase) else print
|
|
237
|
+
if env_vars is UNSET:
|
|
238
|
+
env_vars = os.environ.copy()
|
|
239
|
+
|
|
240
|
+
debug_out(f" . executing {masked_args} at {os.getcwd()=} in {os_env_venv()=}")
|
|
241
|
+
result: subprocess.CompletedProcess | subprocess.CalledProcessError # having: stdout/stderr/returncode
|
|
242
|
+
try:
|
|
243
|
+
result = subprocess.run(all_args,
|
|
244
|
+
stdout=subprocess.PIPE if isinstance(output_lines, list) else None,
|
|
245
|
+
stderr=err_redirect,
|
|
246
|
+
input=console_input.encode(),
|
|
247
|
+
check=True,
|
|
248
|
+
shell=shell,
|
|
249
|
+
env=env_vars)
|
|
250
|
+
except subprocess.CalledProcessError as exc:
|
|
251
|
+
debug_out(f"**** subprocess.run({masked_args}) returned non-zero exit code {exc.returncode}; {exc=}")
|
|
252
|
+
result = exc
|
|
253
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
254
|
+
print_out(f"**** subprocess.run({masked_args}) raised exception {exc}")
|
|
255
|
+
return (126, )[0] # put return/exit code into tuple for global code search
|
|
256
|
+
|
|
257
|
+
if isinstance(output_lines, list):
|
|
258
|
+
if result.stdout:
|
|
259
|
+
output_lines.extend(decoder_splitter(result.stdout))
|
|
260
|
+
if err_redirect == subprocess.PIPE and result.stderr:
|
|
261
|
+
output_lines.append(STDERR_BEG_MARKER)
|
|
262
|
+
output_lines.extend(decoder_splitter(result.stderr))
|
|
263
|
+
output_lines.append(STDERR_END_MARKER)
|
|
264
|
+
|
|
265
|
+
return result.returncode
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
269
|
+
def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
270
|
+
extra_args: Iterable[str] = (), output_lines: list[str] | None = None, exit_on_err: bool = True,
|
|
271
|
+
exit_msg: str = "", app_obj: ConsoleApp | UnsetType | None = None, shell: bool = False,
|
|
272
|
+
env_vars: dict[str, str] | UnsetType | None = UNSET,
|
|
273
|
+
err_redirect: int | None = subprocess.DEVNULL,
|
|
274
|
+
decoder_splitter: Callable[[bytes], list[str]] = output_line_split) -> int:
|
|
275
|
+
""" execute command in the current working directory, optionally capturing console output and exit app on error.
|
|
276
|
+
|
|
277
|
+
:param err_code: error code to pass to the console as exit code if the command set an error code and
|
|
278
|
+
value of the :paramref:`.exit_on_err` argument is `True`.
|
|
279
|
+
:param command_line: command line string to execute. this argument could contain additional command line
|
|
280
|
+
arguments, separated by whitespace characters. alternatively use the argument
|
|
281
|
+
:paramref:`.extra_args` which allows to pass command line argument values,
|
|
282
|
+
with containing space characters.
|
|
283
|
+
:param extra_args: optional iterable of extra command line arguments.
|
|
284
|
+
:param output_lines: optional list extended with the lines printed to stdout/stderr on execution.
|
|
285
|
+
:param exit_on_err: pass False to not exit the app on error.
|
|
286
|
+
:param exit_msg: additional text to print on stdout/console if the app debug level is greater or equal
|
|
287
|
+
to 1 (:data:`~ae.core.DEBUG_LEVEL_ENABLED`) or if an error occurred.
|
|
288
|
+
:param app_obj: :class:`~ae.console.ConsoleApp` instance, used for logging/force-ignorable error.
|
|
289
|
+
:param shell: pass True to execute command in the default OS shell (see :func:`sh_exec`).
|
|
290
|
+
:param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
|
|
291
|
+
:param err_redirect: control how the output on stderr gets captured, redirected and returned. see also
|
|
292
|
+
:paramref:`sh_exec.err_redirect` for more details to the supported argument values.
|
|
293
|
+
if this argument is not specified or has the value :data:`subprocess.DEVNULL` then the
|
|
294
|
+
stderr output will get suppressed on the console and will also not get added to the
|
|
295
|
+
list argument in :paramref:`.output_lines`.
|
|
296
|
+
:param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
|
|
297
|
+
of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
|
|
298
|
+
:return: 0 on success, or if an error occurred the error number set by the executed command.
|
|
299
|
+
"""
|
|
300
|
+
if output_lines is None:
|
|
301
|
+
output_lines = []
|
|
302
|
+
output_len = 0
|
|
303
|
+
else:
|
|
304
|
+
output_len = len(output_lines)
|
|
305
|
+
if app_obj is None:
|
|
306
|
+
app_obj = cast(ConsoleApp, main_app_instance()) # calls app_obj./ConsoleApp.chk() method
|
|
307
|
+
|
|
308
|
+
sh_err = sh_exec(command_line, extra_args=extra_args, output_lines=output_lines, app_obj=app_obj, shell=shell,
|
|
309
|
+
env_vars=env_vars, err_redirect=err_redirect, decoder_splitter=decoder_splitter)
|
|
310
|
+
|
|
311
|
+
if isinstance(app_obj, ConsoleApp) and (app_obj.debug or sh_err and exit_on_err):
|
|
312
|
+
for line in output_lines[output_len:]:
|
|
313
|
+
if app_obj.verbose or not line.startswith("LOG: "): # if verbose show mypy's endless (stderr) log entries
|
|
314
|
+
app_obj.po(" " * 6 + line)
|
|
315
|
+
command = mask_token(f"{command_line} " + " ".join('"' + _a + '"' if " " in _a else _a for _a in extra_args))
|
|
316
|
+
if sh_err == 0:
|
|
317
|
+
app_obj.dpo(f" = successfully executed {command=}")
|
|
318
|
+
else:
|
|
319
|
+
if exit_msg:
|
|
320
|
+
app_obj.po(f" {exit_msg}")
|
|
321
|
+
app_obj.chk(err_code, not exit_on_err, f"sh_exit_if_exec_err({err_code}, {command!r}) error {sh_err}")
|
|
322
|
+
|
|
323
|
+
return sh_err
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: ae namespace module portion shell: shell execution and environment helpers
|
|
5
5
|
Home-page: https://gitlab.com/ae-group/ae_shell
|
|
6
6
|
Author: AndiEcker
|
|
@@ -62,15 +62,15 @@ Dynamic: requires-dist
|
|
|
62
62
|
Dynamic: requires-python
|
|
63
63
|
Dynamic: summary
|
|
64
64
|
|
|
65
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
66
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
67
|
-
# shell 0.3.
|
|
65
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
66
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
67
|
+
# shell 0.3.17
|
|
68
68
|
|
|
69
69
|
[](
|
|
70
70
|
https://gitlab.com/ae-group/ae_shell)
|
|
71
71
|
[](
|
|
73
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.17)
|
|
74
74
|
[](
|
|
75
75
|
https://pypi.org/project/ae-shell/#history)
|
|
76
76
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.
|
|
1
|
+
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.102
|
|
2
2
|
[build-system]
|
|
3
3
|
requires = ["setuptools>=42", "wheel"]
|
|
4
4
|
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[tool.coverage.run]
|
|
7
|
+
omit = ["**/*.kv"] # ignore Kivy's .kv files in pytest unit tests with coverage
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.
|
|
1
|
+
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.102
|
|
2
2
|
""" setup of ae namespace module portion shell: shell execution and environment helpers. """
|
|
3
|
-
import sys
|
|
4
|
-
# noinspection PyUnresolvedReferences
|
|
5
3
|
import pathlib
|
|
6
|
-
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
7
6
|
import setuptools
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
print("SetUp " + __name__ + ": " + sys.executable + str(sys.argv) + f" {sys.path=}")
|
|
11
10
|
|
|
12
|
-
setup_kwargs = {
|
|
11
|
+
setup_kwargs: dict[str, Any] = {
|
|
13
12
|
'author': 'AndiEcker',
|
|
14
13
|
'author_email': 'aecker2@gmail.com',
|
|
15
14
|
'classifiers': [
|
|
@@ -78,10 +77,10 @@ setup_kwargs = {
|
|
|
78
77
|
},
|
|
79
78
|
'python_requires': '>=3.12',
|
|
80
79
|
'url': 'https://gitlab.com/ae-group/ae_shell',
|
|
81
|
-
'version': '0.3.
|
|
80
|
+
'version': '0.3.17',
|
|
82
81
|
'zip_safe': True,
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
if __name__ == "__main__":
|
|
86
85
|
setuptools.setup(**setup_kwargs)
|
|
87
|
-
|
|
86
|
+
...
|