envlockup 0.2.0__tar.gz → 0.2.1__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.
- {envlockup-0.2.0 → envlockup-0.2.1}/PKG-INFO +1 -1
- {envlockup-0.2.0 → envlockup-0.2.1}/pyproject.toml +1 -1
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/__init__.py +1 -1
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/__main__.py +21 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/tests/test_api.py +18 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/.gitignore +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/LICENSE +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/README.md +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/_bech32.py +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/_dotenv.py +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/_keys.py +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/_vault.py +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/errors.py +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/src/envlockup/py.typed +0 -0
- {envlockup-0.2.0 → envlockup-0.2.1}/tests/test_vectors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: envlockup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Read envlockup files from Python: encrypted .env files you can commit, decrypted with your SSH or age key.
|
|
5
5
|
Project-URL: Homepage, https://github.com/denisbilli/envlockup
|
|
6
6
|
Author: denisbilli
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "envlockup"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Read envlockup files from Python: encrypted .env files you can commit, decrypted with your SSH or age key."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -10,6 +10,7 @@ import getpass
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
12
|
import shutil
|
|
13
|
+
import re
|
|
13
14
|
import subprocess
|
|
14
15
|
import sys
|
|
15
16
|
from pathlib import Path
|
|
@@ -66,6 +67,19 @@ def _values(args: argparse.Namespace) -> dict[str, str]:
|
|
|
66
67
|
return load(args.file, identity=args.identity, passphrase=passphrase)
|
|
67
68
|
|
|
68
69
|
|
|
70
|
+
# cmd.exe metacharacters, escaped with ^ (the same rules as the npm CLI and cross-spawn).
|
|
71
|
+
_CMD_META = re.compile(r"""([()\][%!^"`<>&|;, *?])""")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _cmd_escape(arg: str, twice: bool) -> str:
|
|
75
|
+
# Backslashes before a quote, or at the end (before the closing quote), are doubled.
|
|
76
|
+
quoted = '"' + re.sub(r"(\\*)$", r"\1\1", re.sub(r'(\\*)"', r'\1\1\\"', arg)) + '"'
|
|
77
|
+
quoted = _CMD_META.sub(r"^\1", quoted)
|
|
78
|
+
# A batch file that forwards its arguments with %* has cmd.exe parse them a
|
|
79
|
+
# second time, which removes one more level of escaping.
|
|
80
|
+
return _CMD_META.sub(r"^\1", quoted) if twice else quoted
|
|
81
|
+
|
|
82
|
+
|
|
69
83
|
def _run(command: list[str], env: dict[str, str]) -> int:
|
|
70
84
|
if os.name == "posix":
|
|
71
85
|
# Replace this process, so the command gets signals and keeps our PID
|
|
@@ -78,6 +92,13 @@ def _run(command: list[str], env: dict[str, str]) -> int:
|
|
|
78
92
|
# Windows: resolve .exe/.cmd through PATHEXT, then wait for the child.
|
|
79
93
|
exe = shutil.which(command[0], path=env.get("PATH")) or command[0]
|
|
80
94
|
try:
|
|
95
|
+
if re.search(r"\.(cmd|bat)$", exe, re.IGNORECASE):
|
|
96
|
+
# Batch files run through cmd.exe, which would interpret & | % and
|
|
97
|
+
# friends in the arguments: escape them, twice, since launchers pass
|
|
98
|
+
# them on with %*.
|
|
99
|
+
line = " ".join([_CMD_META.sub(r"^\1", exe), *(_cmd_escape(a, True) for a in command[1:])])
|
|
100
|
+
comspec = env.get("COMSPEC") or os.environ.get("COMSPEC") or "cmd.exe"
|
|
101
|
+
return subprocess.call(f'{comspec} /d /s /c "{line}"', env=env)
|
|
81
102
|
return subprocess.call([exe, *command[1:]], env=env)
|
|
82
103
|
except OSError as err:
|
|
83
104
|
print(f"envlockup: cannot run {command[0]}: {err.strerror}", file=sys.stderr)
|
|
@@ -141,6 +141,24 @@ def test_cli_run(env_file, cli_env):
|
|
|
141
141
|
assert json.loads(r.stdout)["EMPTY"] == ""
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
TRICKY_ARGS = ["a&b", "50%", "%PATH%", "x^y", 'say "hi"', 'q"&"q', "back\\slash\\", "with space", "", "!bang!", "(p)", "a|b", "<lt>"]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.mark.skipif(os.name != "nt", reason="Windows only")
|
|
148
|
+
def test_cli_run_passes_arguments_unchanged_to_cmd_scripts(env_file, cli_env, tmp_path):
|
|
149
|
+
printer = tmp_path / "argv.py"
|
|
150
|
+
printer.write_text("import json, sys\nprint(json.dumps(sys.argv[1:]))\n", encoding="utf-8")
|
|
151
|
+
for sub in ("scripts", os.path.join("node_modules", ".bin")):
|
|
152
|
+
(tmp_path / sub).mkdir(parents=True, exist_ok=True)
|
|
153
|
+
script = tmp_path / sub / "argv.cmd"
|
|
154
|
+
script.write_text(f'@"{sys.executable}" "{printer}" %*\r\n', encoding="utf-8")
|
|
155
|
+
r = cli("run", "--", str(script), *TRICKY_ARGS, env=cli_env, cwd=env_file.parent)
|
|
156
|
+
assert r.returncode == 0, r.stderr
|
|
157
|
+
assert json.loads(r.stdout) == TRICKY_ARGS, sub
|
|
158
|
+
r = cli("run", "--", sys.executable, str(printer), *TRICKY_ARGS, env=cli_env, cwd=env_file.parent)
|
|
159
|
+
assert json.loads(r.stdout) == TRICKY_ARGS
|
|
160
|
+
|
|
161
|
+
|
|
144
162
|
def test_cli_run_exit_code_and_missing_command(env_file, cli_env):
|
|
145
163
|
r = cli("run", "--", sys.executable, "-c", "raise SystemExit(7)", env=cli_env, cwd=env_file.parent)
|
|
146
164
|
assert r.returncode == 7
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|