ae-shell 0.3.5__tar.gz → 0.3.6__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.6}/PKG-INFO +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.6}/README.md +5 -5
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae/shell.py +50 -19
- {ae_shell-0.3.5 → ae_shell-0.3.6/ae_shell.egg-info}/PKG-INFO +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.6}/setup.py +6 -6
- {ae_shell-0.3.5 → ae_shell-0.3.6}/tests/test_shell.py +14 -1
- {ae_shell-0.3.5 → ae_shell-0.3.6}/LICENSE.md +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae_shell.egg-info/SOURCES.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae_shell.egg-info/dependency_links.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae_shell.egg-info/requires.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae_shell.egg-info/top_level.txt +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/ae_shell.egg-info/zip-safe +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/pyproject.toml +0 -0
- {ae_shell-0.3.5 → ae_shell-0.3.6}/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.6
|
|
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.6
|
|
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.6)
|
|
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.6
|
|
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.6)
|
|
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.6'
|
|
208
209
|
|
|
209
210
|
|
|
210
211
|
COMMIT_MSG_FILE_NAME = '.commit_msg.txt' #: name of the file containing the commit message
|
|
@@ -1033,6 +1034,39 @@ 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 an iterable 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.index(tok_end, start)
|
|
1064
|
+
line = line[:start + 3] + "***-masked-token-***" + line[end - 3:]
|
|
1065
|
+
lines[idx] = line
|
|
1066
|
+
|
|
1067
|
+
return lines[0] if is_str_arg else lines
|
|
1068
|
+
|
|
1069
|
+
|
|
1036
1070
|
def owner_project_from_url(remote_url: str) -> str:
|
|
1037
1071
|
""" determine the owner and project name path from the specified git remote repository url.
|
|
1038
1072
|
|
|
@@ -1069,7 +1103,7 @@ def project_name_version(imp_or_pkg_name: str, packages_versions: Iterable[str])
|
|
|
1069
1103
|
|
|
1070
1104
|
|
|
1071
1105
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
1072
|
-
def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "",
|
|
1106
|
+
def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: str = "",
|
|
1073
1107
|
lines_output: Optional[list[str]] = None, main_app: Optional[Any] = None, shell: bool = False,
|
|
1074
1108
|
env_vars: Optional[dict[str, str]] = None) -> int:
|
|
1075
1109
|
""" execute command in the current working directory of the OS console/shell.
|
|
@@ -1093,7 +1127,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1093
1127
|
merge_err = bool(lines_output) # == -''- and len(lines_output) > 0
|
|
1094
1128
|
print_out = main_app.po if main_app else print if main_app is None else dummy_function
|
|
1095
1129
|
debug_out = main_app.dpo if main_app else dummy_function
|
|
1096
|
-
debug_out(f" . executing at {os.getcwd()}: {args}")
|
|
1130
|
+
debug_out(f" . executing at {os.getcwd()}: {mask_token(args)}")
|
|
1097
1131
|
|
|
1098
1132
|
result: Union[subprocess.CompletedProcess, subprocess.CalledProcessError] # having: stdout/stderr/returncode
|
|
1099
1133
|
try:
|
|
@@ -1105,10 +1139,10 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1105
1139
|
shell=shell,
|
|
1106
1140
|
env=env_vars)
|
|
1107
1141
|
except subprocess.CalledProcessError as ex: # pragma: no cover
|
|
1108
|
-
debug_out(f"**** subprocess.run({args
|
|
1142
|
+
debug_out(f"**** subprocess.run({mask_token(args)}) returned non-zero exit code {ex.returncode}; {ex=}")
|
|
1109
1143
|
result = ex
|
|
1110
1144
|
except Exception as ex: # pylint: disable=broad-except # pragma: no cover
|
|
1111
|
-
print_out(f"**** subprocess.run({args}) raised exception {ex}")
|
|
1145
|
+
print_out(f"**** subprocess.run({mask_token(args)}) raised exception {ex}")
|
|
1112
1146
|
return 126
|
|
1113
1147
|
|
|
1114
1148
|
if ret_out:
|
|
@@ -1125,7 +1159,7 @@ def sh_exec(command_line: str, extra_args: Iterable = (), console_input: str = "
|
|
|
1125
1159
|
|
|
1126
1160
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
1127
1161
|
def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
1128
|
-
extra_args: Iterable = (), lines_output: Optional[list[str]] = None,
|
|
1162
|
+
extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
|
|
1129
1163
|
exit_on_err: bool = True, exit_msg: str = "", shell: bool = False,
|
|
1130
1164
|
env_vars: Optional[dict[str, str]] = None) -> int:
|
|
1131
1165
|
""" execute command in the current working directory of the OS console/shell, dump error, and exit app if needed.
|
|
@@ -1160,19 +1194,19 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
|
1160
1194
|
main_app.po(" " * 6 + line)
|
|
1161
1195
|
msg = f"command: {command_line} " + " ".join('"' + arg + '"' if " " in arg else arg for arg in extra_args)
|
|
1162
1196
|
if not sh_err:
|
|
1163
|
-
main_app.dpo(f" = successfully executed {msg}")
|
|
1197
|
+
main_app.dpo(f" = successfully executed {mask_token(msg)}")
|
|
1164
1198
|
else:
|
|
1165
1199
|
if exit_msg:
|
|
1166
1200
|
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}")
|
|
1201
|
+
check_if(err_code, not exit_on_err, f"sh_exit_if_exec_err error {sh_err} in {mask_token(msg)}") # app exit
|
|
1168
1202
|
|
|
1169
1203
|
return sh_err
|
|
1170
1204
|
|
|
1171
1205
|
|
|
1172
1206
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
1173
1207
|
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]:
|
|
1208
|
+
extra_args: Iterable[str] = (), lines_output: Optional[list[str]] = None,
|
|
1209
|
+
exit_on_err: bool = False, log_enable_dir: str = "") -> list[str]:
|
|
1176
1210
|
""" execute git command with optional git trace output, returning the stdout lines cleaned from any trace messages.
|
|
1177
1211
|
|
|
1178
1212
|
:param err_code: error code to pass to the console as exit code if :paramref:`.exit_on_err` is True.
|
|
@@ -1211,8 +1245,9 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
|
|
|
1211
1245
|
sh_log(command_line, extra_args=extra_args, cl_err=cl_err, lines_output=lines_output, log_file_paths=log_files)
|
|
1212
1246
|
|
|
1213
1247
|
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
|
-
|
|
1248
|
+
cmd_line = mask_token([command_line] + list(extra_args))
|
|
1249
|
+
main_app.vpo(f" # ignored error {cl_err} of `{cmd_line}` and git trace {env_vars=}")
|
|
1250
|
+
lines_output.insert(0, EXEC_GIT_ERR_PREFIX + str(cl_err) + f" in {cmd_line}")
|
|
1216
1251
|
|
|
1217
1252
|
if STDERR_BEG_MARKER in lines_output and ( # output marker only if stderr not got merged/called w/ lines_output==[]
|
|
1218
1253
|
git_debug or any(os.environ.get(_, "0") in ("true", "1", "2") for _ in git_trace_vars)):
|
|
@@ -1224,7 +1259,7 @@ def sh_exit_if_git_err(err_code: int, command_line: str,
|
|
|
1224
1259
|
main_app.po(sep + lines_output[line_no])
|
|
1225
1260
|
lines_output[:] = lines_output[:start] # del output[start:]
|
|
1226
1261
|
|
|
1227
|
-
return lines_output
|
|
1262
|
+
return list(mask_token(lines_output))
|
|
1228
1263
|
|
|
1229
1264
|
|
|
1230
1265
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
@@ -1250,11 +1285,7 @@ def sh_log(comment_or_command: str, extra_args: Iterable[str] = (), cl_err: int
|
|
|
1250
1285
|
(f" * {cl_err=}" + sep if cl_err else "") +
|
|
1251
1286
|
(" " + (sep + " ").join(lines_output) + sep if lines_output else ""))
|
|
1252
1287
|
|
|
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:]
|
|
1288
|
+
log_lines = mask_token(log_lines)
|
|
1258
1289
|
|
|
1259
1290
|
for log_path in log_file_paths or sh_logs(log_name_prefix=log_name_prefix):
|
|
1260
1291
|
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.6
|
|
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.6
|
|
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.6)
|
|
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.6\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.6)\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.6',
|
|
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,19 @@ 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 with an @/ampersand and 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 with an @/ampersand and 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
|
+
|
|
1455
1468
|
def test_owner_project_from_url(self):
|
|
1456
1469
|
assert owner_project_from_url("owner/project") == "owner/project"
|
|
1457
1470
|
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
|