ae-shell 0.3.5__tar.gz → 0.3.7__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.5/ae_shell.egg-info → ae_shell-0.3.7}/PKG-INFO +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.7}/README.md +5 -5
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae/shell.py +52 -19
- {ae_shell-0.3.5 → ae_shell-0.3.7/ae_shell.egg-info}/PKG-INFO +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.7}/setup.py +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.7}/tests/test_shell.py +18 -1
- {ae_shell-0.3.5 → ae_shell-0.3.7}/LICENSE.md +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae_shell.egg-info/SOURCES.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae_shell.egg-info/dependency_links.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae_shell.egg-info/requires.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae_shell.egg-info/top_level.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/ae_shell.egg-info/zip-safe +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/pyproject.toml +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.7}/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.7
|
|
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
|
|
@@ -63,15 +63,15 @@ Dynamic: provides-extra
|
|
|
63
63
|
Dynamic: requires-python
|
|
64
64
|
Dynamic: summary
|
|
65
65
|
|
|
66
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
67
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
68
|
-
# shell 0.3.
|
|
66
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->
|
|
67
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
|
|
68
|
+
# shell 0.3.7
|
|
69
69
|
|
|
70
70
|
[](
|
|
71
71
|
https://gitlab.com/ae-group/ae_shell)
|
|
72
72
|
[](
|
|
74
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)
|
|
75
75
|
[](
|
|
76
76
|
https://pypi.org/project/ae-shell/#history)
|
|
77
77
|
|
|
@@ -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.101 -->
|
|
2
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
|
|
3
|
+
# shell 0.3.7
|
|
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.7)
|
|
10
10
|
[](
|
|
11
11
|
https://pypi.org/project/ae-shell/#history)
|
|
12
12
|
|
|
@@ -174,6 +174,7 @@ this section includes various other utility functions and classes.
|
|
|
174
174
|
- :func:`get_main_app`: retrieves the main application instance, or a mock instance if one does not exist.
|
|
175
175
|
- :func:`get_pypi_versions`: determines all available release versions of a package on PyPI.
|
|
176
176
|
- :func:`hint`: provides a hint message based on the provided arguments.
|
|
177
|
+
- :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs or error messages.
|
|
177
178
|
- :func:`prg_git_project_path`: determines the project root path from the current working directory.
|
|
178
179
|
|
|
179
180
|
- :class:`MockedMainApp`: a mock class for a main application instance.
|
|
@@ -192,7 +193,7 @@ import tempfile
|
|
|
192
193
|
|
|
193
194
|
from contextlib import contextmanager
|
|
194
195
|
from urllib.parse import urlparse
|
|
195
|
-
from typing import
|
|
196
|
+
from typing import Any, Callable, Iterable, Iterator, MutableMapping, Optional, Union, cast, overload
|
|
196
197
|
|
|
197
198
|
import requests
|
|
198
199
|
from packaging.version import Version
|
|
@@ -204,7 +205,7 @@ from ae.core import main_app_instance
|
|
|
204
205
|
from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
|
|
205
206
|
|
|
206
207
|
|
|
207
|
-
__version__ = '0.3.
|
|
208
|
+
__version__ = '0.3.7'
|
|
208
209
|
|
|
209
210
|
|
|
210
211
|
COMMIT_MSG_FILE_NAME = '.commit_msg.txt' #: name of the file containing the commit message
|
|
@@ -1033,6 +1034,41 @@ def in_venv(name: str = "") -> Iterator[None]:
|
|
|
1033
1034
|
activate_venv(old_venv)
|
|
1034
1035
|
|
|
1035
1036
|
|
|
1037
|
+
@overload
|
|
1038
|
+
def mask_token(text: str) -> str: ...
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
@overload
|
|
1042
|
+
def mask_token(text: list[str]) -> list[str]: ...
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def mask_token(text: Union[str, list[str]]) -> Union[str, list[str]]:
|
|
1046
|
+
""" hide most parts of any GitHub/GitHub tokens found in the specified text/-lines.
|
|
1047
|
+
|
|
1048
|
+
:param text: text block, specified either as str object or as a list of str objects (lines),
|
|
1049
|
+
to detect tokens within, to hide/mask the most part of them.
|
|
1050
|
+
:return: text block with without the complete tokens.
|
|
1051
|
+
|
|
1052
|
+
.. note:: see also :func:`ae.base.mask_url` to hide passwords and tokens in URLs.
|
|
1053
|
+
"""
|
|
1054
|
+
if is_str_arg := isinstance(text, str):
|
|
1055
|
+
lines = [text]
|
|
1056
|
+
else:
|
|
1057
|
+
lines = list(text) # copy to not change text list content
|
|
1058
|
+
|
|
1059
|
+
for tok_beg, tok_end in (('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
|
|
1060
|
+
for idx, line in enumerate(lines):
|
|
1061
|
+
while tok_beg in line: # hide the GitLab/GitHub private token, e.g. from git-push-urls with authentication
|
|
1062
|
+
start = line.index(tok_beg)
|
|
1063
|
+
end = line.find(tok_end, start)
|
|
1064
|
+
if end == -1:
|
|
1065
|
+
break
|
|
1066
|
+
line = line[:start + 3] + "***-masked-token-***" + line[end - 3:]
|
|
1067
|
+
lines[idx] = line
|
|
1068
|
+
|
|
1069
|
+
return lines[0] if is_str_arg else lines
|
|
1070
|
+
|
|
1071
|
+
|
|
1036
1072
|
def owner_project_from_url(remote_url: str) -> str:
|
|
1037
1073
|
""" determine the owner and project name path from the specified git remote repository url.
|
|
1038
1074
|
|
|
@@ -1069,7 +1105,7 @@ def project_name_version(imp_or_pkg_name: str, packages_versions: Iterable[str])
|
|
|
1069
1105
|
|
|
1070
1106
|
|
|
1071
1107
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
1072
|
-
def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "",
|
|
1108
|
+
def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
|
|
1073
1109
|
lines_output: Optional[list[str]] = None, main_app: Optional[Any] = None, shell: bool = False,
|
|
1074
1110
|
env_vars: Optional[dict[str, str]] = None) -> int:
|
|
1075
1111
|
""" execute command in the current working directory of the OS console/shell.
|
|
@@ -1093,7 +1129,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1093
1129
|
merge_err = bool(lines_output) # == -''- and len(lines_output) > 0
|
|
1094
1130
|
print_out = main_app.po if main_app else print if main_app is None else dummy_function
|
|
1095
1131
|
debug_out = main_app.dpo if main_app else dummy_function
|
|
1096
|
-
debug_out(f" . executing at {os.getcwd()}: {args}")
|
|
1132
|
+
debug_out(f" . executing at {os.getcwd()}: {mask_token(args)}")
|
|
1097
1133
|
|
|
1098
1134
|
result: Union[subprocess.CompletedProcess, subprocess.CalledProcessError] # having: stdout/stderr/returncode
|
|
1099
1135
|
try:
|
|
@@ -1105,10 +1141,10 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1105
1141
|
shell=shell,
|
|
1106
1142
|
env=env_vars)
|
|
1107
1143
|
except subprocess.CalledProcessError as ex: # pragma: no cover
|
|
1108
|
-
debug_out(f"**** subprocess.run({args
|
|
1144
|
+
debug_out(f"**** subprocess.run({mask_token(args)}) returned non-zero exit code {ex.returncode}; {ex=}")
|
|
1109
1145
|
result = ex
|
|
1110
1146
|
except Exception as ex: # pylint: disable=broad-except # pragma: no cover
|
|
1111
|
-
print_out(f"**** subprocess.run({args}) raised exception {ex}")
|
|
1147
|
+
print_out(f"**** subprocess.run({mask_token(args)}) raised exception {ex}")
|
|
1112
1148
|
return 126
|
|
1113
1149
|
|
|
1114
1150
|
if ret_out:
|
|
@@ -1125,7 +1161,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1125
1161
|
|
|
1126
1162
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
1127
1163
|
def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
1128
|
-
extra_args: Iterable = (), lines_output: Optional[list[str]] = None,
|
|
1164
|
+
extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
|
|
1129
1165
|
exit_on_err: bool = True, exit_msg: str = "", shell: bool = False,
|
|
1130
1166
|
env_vars: Optional[dict[str, str]] = None) -> int:
|
|
1131
1167
|
""" execute command in the current working directory of the OS console/shell, dump error, and exit app if needed.
|
|
@@ -1160,19 +1196,19 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
|
1160
1196
|
main_app.po(" " * 6 + line)
|
|
1161
1197
|
msg = f"command: {command_line} " + " ".join('"' + arg + '"' if " " in arg else arg for arg in extra_args)
|
|
1162
1198
|
if not sh_err:
|
|
1163
|
-
main_app.dpo(f" = successfully executed {msg}")
|
|
1199
|
+
main_app.dpo(f" = successfully executed {mask_token(msg)}")
|
|
1164
1200
|
else:
|
|
1165
1201
|
if exit_msg:
|
|
1166
1202
|
main_app.po(f" {exit_msg}")
|
|
1167
|
-
check_if(err_code, not exit_on_err, f"sh_exit_if_exec_err error {sh_err} in {msg}")
|
|
1203
|
+
check_if(err_code, not exit_on_err, f"sh_exit_if_exec_err error {sh_err} in {mask_token(msg)}") # app exit
|
|
1168
1204
|
|
|
1169
1205
|
return sh_err
|
|
1170
1206
|
|
|
1171
1207
|
|
|
1172
1208
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
1173
1209
|
def sh_exit_if_git_err(err_code: int, command_line: str,
|
|
1174
|
-
extra_args: Iterable = (), lines_output: Optional[list[str]] = None,
|
|
1175
|
-
log_enable_dir: str = "") -> list[str]:
|
|
1210
|
+
extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
|
|
1211
|
+
exit_on_err: bool = False, log_enable_dir: str = "") -> list[str]:
|
|
1176
1212
|
""" execute git command with optional git trace output, returning the stdout lines cleaned from any trace messages.
|
|
1177
1213
|
|
|
1178
1214
|
:param err_code: error code to pass to the console as exit code if :paramref:`.exit_on_err` is True.
|
|
@@ -1211,8 +1247,9 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
|
|
|
1211
1247
|
sh_log(command_line, extra_args=extra_args, cl_err=cl_err, lines_output=lines_output, log_file_paths=log_files)
|
|
1212
1248
|
|
|
1213
1249
|
if cl_err: # if cl_err and exit_on_err then it would have exit the Python interpreter (so never would run to here)
|
|
1214
|
-
|
|
1215
|
-
|
|
1250
|
+
cmd_line = mask_token([command_line] + list(extra_args))
|
|
1251
|
+
main_app.vpo(f" # ignored error {cl_err} of `{cmd_line}` and git trace {env_vars=}")
|
|
1252
|
+
lines_output.insert(0, EXEC_GIT_ERR_PREFIX + str(cl_err) + f" in {cmd_line}")
|
|
1216
1253
|
|
|
1217
1254
|
if STDERR_BEG_MARKER in lines_output and ( # output marker only if stderr not got merged/called w/ lines_output==[]
|
|
1218
1255
|
git_debug or any(os.environ.get(_, "0") in ("true", "1", "2") for _ in git_trace_vars)):
|
|
@@ -1224,7 +1261,7 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
|
|
|
1224
1261
|
main_app.po(sep + lines_output[line_no])
|
|
1225
1262
|
lines_output[:] = lines_output[:start] # del output[start:]
|
|
1226
1263
|
|
|
1227
|
-
return lines_output
|
|
1264
|
+
return list(mask_token(lines_output))
|
|
1228
1265
|
|
|
1229
1266
|
|
|
1230
1267
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
@@ -1250,11 +1287,7 @@ def sh_log(comment_or_command: str, extra_args: Iterable[str] = (), cl_err: int
|
|
|
1250
1287
|
(f" * {cl_err=}" + sep if cl_err else "") +
|
|
1251
1288
|
(" " + (sep + " ").join(lines_output) + sep if lines_output else ""))
|
|
1252
1289
|
|
|
1253
|
-
|
|
1254
|
-
while tok_beg in log_lines: # hide the gitlab private token, e.g. from git-push-urls with authentication
|
|
1255
|
-
start = log_lines.index(tok_beg)
|
|
1256
|
-
end = log_lines.index(tok_end, start)
|
|
1257
|
-
log_lines = log_lines[:start] + "private-token-" + log_lines[end - 3:]
|
|
1290
|
+
log_lines = mask_token(log_lines)
|
|
1258
1291
|
|
|
1259
1292
|
for log_path in log_file_paths or sh_logs(log_name_prefix=log_name_prefix):
|
|
1260
1293
|
write_file(log_path, log_lines, extra_mode='a')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
@@ -63,15 +63,15 @@ Dynamic: provides-extra
|
|
|
63
63
|
Dynamic: requires-python
|
|
64
64
|
Dynamic: summary
|
|
65
65
|
|
|
66
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
67
|
-
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
68
|
-
# shell 0.3.
|
|
66
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->
|
|
67
|
+
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->
|
|
68
|
+
# shell 0.3.7
|
|
69
69
|
|
|
70
70
|
[](
|
|
71
71
|
https://gitlab.com/ae-group/ae_shell)
|
|
72
72
|
[](
|
|
74
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)
|
|
75
75
|
[](
|
|
76
76
|
https://pypi.org/project/ae-shell/#history)
|
|
77
77
|
|
|
@@ -23,15 +23,15 @@ setup_kwargs = {
|
|
|
23
23
|
'install_requires': [],
|
|
24
24
|
'keywords': ['configuration', 'development', 'environment', 'productivity'],
|
|
25
25
|
'license': 'GPL-3.0-or-later',
|
|
26
|
-
'long_description': ('<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.
|
|
27
|
-
'<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.
|
|
28
|
-
'# shell 0.3.
|
|
26
|
+
'long_description': ('<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.101 -->\n'
|
|
27
|
+
'<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.22 -->\n'
|
|
28
|
+
'# shell 0.3.7\n'
|
|
29
29
|
'\n'
|
|
30
30
|
'[](\n'
|
|
31
31
|
' https://gitlab.com/ae-group/ae_shell)\n'
|
|
32
32
|
'[](\n'
|
|
34
|
+
' https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.7)\n'
|
|
35
35
|
'[](\n'
|
|
36
36
|
' https://pypi.org/project/ae-shell/#history)\n'
|
|
37
37
|
'\n'
|
|
@@ -108,7 +108,7 @@ setup_kwargs = {
|
|
|
108
108
|
'Source': 'https://ae.readthedocs.io/en/latest/_modules/ae/shell.html'},
|
|
109
109
|
'python_requires': '>=3.9',
|
|
110
110
|
'url': 'https://gitlab.com/ae-group/ae_shell',
|
|
111
|
-
'version': '0.3.
|
|
111
|
+
'version': '0.3.7',
|
|
112
112
|
'zip_safe': True,
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -39,7 +39,7 @@ from ae.shell import (
|
|
|
39
39
|
git_current_branch, git_diff, git_fetch, git_init_if_needed, git_merge, git_push,
|
|
40
40
|
git_remote_domain_group, git_remotes, git_renew_remotes, git_status,
|
|
41
41
|
git_tag_add, git_ref_in_branch, git_tag_list, git_tag_remotes, git_uncommitted,
|
|
42
|
-
hint, in_os_env, in_prj_dir_venv, in_venv, owner_project_from_url, project_name_version,
|
|
42
|
+
hint, in_os_env, in_prj_dir_venv, in_venv, mask_token, owner_project_from_url, project_name_version,
|
|
43
43
|
sh_exec, sh_exit_if_exec_err, sh_exit_if_git_err, sh_log, sh_logs,
|
|
44
44
|
temp_context_cleanup, temp_context_folders, temp_context_get_or_create, _temp_folders, venv_bin_path,
|
|
45
45
|
MockedMainApp)
|
|
@@ -1452,6 +1452,23 @@ class TestHelpers:
|
|
|
1452
1452
|
assert not hint("hint command", _hint_tst_callable, "extra message")
|
|
1453
1453
|
assert not hint("hint command", _hint_tst_callable.__name__, "extra message")
|
|
1454
1454
|
|
|
1455
|
+
def test_mask_token(self):
|
|
1456
|
+
token = "glpat-gitlab token format ending at the @/ampersand directly followed by the gitlab.com domain"
|
|
1457
|
+
text = "a text block containing a gitlab URL with a token: https://UsaNäm:" + token + "@gitlab.com"
|
|
1458
|
+
|
|
1459
|
+
assert token not in mask_token(text)
|
|
1460
|
+
assert token not in mask_token([text])[0]
|
|
1461
|
+
|
|
1462
|
+
token = "ghp_-github token format ending at the @/ampersand directly followed by the github.com domain"
|
|
1463
|
+
text = "a text block containing a github URL with a token: https://YouSaNem:" + token + "@github.com"
|
|
1464
|
+
|
|
1465
|
+
assert token not in mask_token(text)
|
|
1466
|
+
assert token not in mask_token([text])[0]
|
|
1467
|
+
|
|
1468
|
+
text = "skip masking of text blocks with a start token like ghp_ or glpat- but missing end token"
|
|
1469
|
+
|
|
1470
|
+
assert mask_token(text) == text # neither throws str.index()-ValueError nor stuck in endless-loop
|
|
1471
|
+
|
|
1455
1472
|
def test_owner_project_from_url(self):
|
|
1456
1473
|
assert owner_project_from_url("owner/project") == "owner/project"
|
|
1457
1474
|
assert owner_project_from_url("owner/project.git") == "owner/project"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|