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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ae_shell
3
- Version: 0.3.5
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.100 -->
67
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
68
- # shell 0.3.5
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
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
71
71
  https://gitlab.com/ae-group/ae_shell)
72
72
  [![LatestPyPIrelease](
73
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](
74
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
73
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.6?logo=python)](
74
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.6)
75
75
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
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.100 -->
2
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
3
- # shell 0.3.5
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
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
6
6
  https://gitlab.com/ae-group/ae_shell)
7
7
  [![LatestPyPIrelease](
8
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](
9
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
8
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.6?logo=python)](
9
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.6)
10
10
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
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 Optional, Iterator, Iterable, cast, Callable, Any, Union, MutableMapping
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.5'
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=}) returned non-zero exit code {ex.returncode}; {ex=}")
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}") # app exit
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, exit_on_err: bool = False,
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
- main_app.vpo(f" # ignored error {cl_err} of {command_line=} with {extra_args=} and git trace {env_vars=}")
1215
- lines_output.insert(0, EXEC_GIT_ERR_PREFIX + str(cl_err) + f" in {command_line=} with {extra_args=}")
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
- for tok_beg, tok_end in (('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
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.5
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.100 -->
67
- <!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->
68
- # shell 0.3.5
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
  [![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](
71
71
  https://gitlab.com/ae-group/ae_shell)
72
72
  [![LatestPyPIrelease](
73
- https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](
74
- https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)
73
+ https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.6?logo=python)](
74
+ https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.6)
75
75
  [![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](
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.100 -->\n'
27
- '<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.21 -->\n'
28
- '# shell 0.3.5\n'
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
  '[![GitLab develop](https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/develop?logo=python)](\n'
31
31
  ' https://gitlab.com/ae-group/ae_shell)\n'
32
32
  '[![LatestPyPIrelease](\n'
33
- ' https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.5?logo=python)](\n'
34
- ' https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.5)\n'
33
+ ' https://img.shields.io/gitlab/pipeline/ae-group/ae_shell/release0.3.6?logo=python)](\n'
34
+ ' https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.6)\n'
35
35
  '[![PyPIVersions](https://img.shields.io/pypi/v/ae_shell)](\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.5',
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