envstack 0.9.2__tar.gz → 0.9.4__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.
- {envstack-0.9.2/lib/envstack.egg-info → envstack-0.9.4}/PKG-INFO +10 -7
- {envstack-0.9.2 → envstack-0.9.4}/README.md +9 -6
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/__init__.py +2 -2
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/cli.py +26 -6
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/config.py +1 -1
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/encrypt.py +8 -8
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/env.py +7 -3
- envstack-0.9.4/lib/envstack/envshell.py +143 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/exceptions.py +1 -1
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/logger.py +1 -1
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/node.py +2 -2
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/path.py +1 -1
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/util.py +1 -6
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack/wrapper.py +62 -57
- {envstack-0.9.2 → envstack-0.9.4/lib/envstack.egg-info}/PKG-INFO +10 -7
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/SOURCES.txt +3 -1
- {envstack-0.9.2 → envstack-0.9.4}/setup.py +2 -2
- {envstack-0.9.2 → envstack-0.9.4}/tests/test_cmds.py +53 -88
- {envstack-0.9.2 → envstack-0.9.4}/tests/test_encrypt.py +1 -1
- {envstack-0.9.2 → envstack-0.9.4}/tests/test_env.py +10 -10
- {envstack-0.9.2 → envstack-0.9.4}/tests/test_node.py +2 -2
- {envstack-0.9.2 → envstack-0.9.4}/tests/test_util.py +1 -1
- envstack-0.9.4/tests/test_wrapper.py +141 -0
- {envstack-0.9.2 → envstack-0.9.4}/LICENSE +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/dist.json +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/dependency_links.txt +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/entry_points.txt +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/not-zip-safe +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/requires.txt +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/lib/envstack.egg-info/top_level.txt +0 -0
- {envstack-0.9.2 → envstack-0.9.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: envstack
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: Stacked environment variable management system
|
|
5
5
|
Home-page: http://github.com/rsgalloway/envstack
|
|
6
6
|
Author: Ryan Galloway
|
|
@@ -112,7 +112,9 @@ STACK=default
|
|
|
112
112
|
If you are not seeing the above output, make sure the `default.env` stack file
|
|
113
113
|
is in `${ENVPATH}` or the current working directory.
|
|
114
114
|
|
|
115
|
-
> NOTE: The name of the current stack will always be stored in `${STACK}
|
|
115
|
+
> NOTE: The name of the current stack will always be stored in `${STACK}`.
|
|
116
|
+
|
|
117
|
+
ENV is the tier, STACK is the namespace.
|
|
116
118
|
|
|
117
119
|
Environments can be combined, or stacked, in order of priority (variables
|
|
118
120
|
defined in stacks flow from higher scope to lower scope, left to right):
|
|
@@ -371,21 +373,21 @@ Then use `keys.env` to encrypt any other environment files:
|
|
|
371
373
|
$ ./keys.env -- envstack -eo encrypted.env
|
|
372
374
|
```
|
|
373
375
|
|
|
374
|
-
To decrypt,
|
|
376
|
+
To decrypt, run the command inside the `keys` environment again:
|
|
375
377
|
|
|
376
378
|
```bash
|
|
377
|
-
$ envstack
|
|
379
|
+
$ ./keys.env -- envstack encrypted -r HELLO
|
|
378
380
|
HELLO=world
|
|
379
381
|
```
|
|
380
382
|
|
|
381
|
-
Or
|
|
383
|
+
Or add `keys` to the env stack:
|
|
382
384
|
|
|
383
385
|
```bash
|
|
384
|
-
$
|
|
386
|
+
$ envstack keys encrypted -r HELLO
|
|
385
387
|
HELLO=world
|
|
386
388
|
```
|
|
387
389
|
|
|
388
|
-
Or include `keys
|
|
390
|
+
Or automatically include `keys`:
|
|
389
391
|
|
|
390
392
|
```yaml
|
|
391
393
|
include: [keys]
|
|
@@ -614,6 +616,7 @@ The following environment variables are used to help manage functionality:
|
|
|
614
616
|
| DEFAULT_ENV_STACK | Name of the default environment stack (default) |
|
|
615
617
|
| ENVPATH | Colon-separated paths to search for stack files |
|
|
616
618
|
| IGNORE_MISSING | Ignore missing stack files when resolving environments |
|
|
619
|
+
| INTERACTIVE | Run one-off commands in an interactive shell |
|
|
617
620
|
| STACK | Stores the name of the current environment stack |
|
|
618
621
|
|
|
619
622
|
# Tests
|
|
@@ -88,7 +88,9 @@ STACK=default
|
|
|
88
88
|
If you are not seeing the above output, make sure the `default.env` stack file
|
|
89
89
|
is in `${ENVPATH}` or the current working directory.
|
|
90
90
|
|
|
91
|
-
> NOTE: The name of the current stack will always be stored in `${STACK}
|
|
91
|
+
> NOTE: The name of the current stack will always be stored in `${STACK}`.
|
|
92
|
+
|
|
93
|
+
ENV is the tier, STACK is the namespace.
|
|
92
94
|
|
|
93
95
|
Environments can be combined, or stacked, in order of priority (variables
|
|
94
96
|
defined in stacks flow from higher scope to lower scope, left to right):
|
|
@@ -347,21 +349,21 @@ Then use `keys.env` to encrypt any other environment files:
|
|
|
347
349
|
$ ./keys.env -- envstack -eo encrypted.env
|
|
348
350
|
```
|
|
349
351
|
|
|
350
|
-
To decrypt,
|
|
352
|
+
To decrypt, run the command inside the `keys` environment again:
|
|
351
353
|
|
|
352
354
|
```bash
|
|
353
|
-
$ envstack
|
|
355
|
+
$ ./keys.env -- envstack encrypted -r HELLO
|
|
354
356
|
HELLO=world
|
|
355
357
|
```
|
|
356
358
|
|
|
357
|
-
Or
|
|
359
|
+
Or add `keys` to the env stack:
|
|
358
360
|
|
|
359
361
|
```bash
|
|
360
|
-
$
|
|
362
|
+
$ envstack keys encrypted -r HELLO
|
|
361
363
|
HELLO=world
|
|
362
364
|
```
|
|
363
365
|
|
|
364
|
-
Or include `keys
|
|
366
|
+
Or automatically include `keys`:
|
|
365
367
|
|
|
366
368
|
```yaml
|
|
367
369
|
include: [keys]
|
|
@@ -590,6 +592,7 @@ The following environment variables are used to help manage functionality:
|
|
|
590
592
|
| DEFAULT_ENV_STACK | Name of the default environment stack (default) |
|
|
591
593
|
| ENVPATH | Colon-separated paths to search for stack files |
|
|
592
594
|
| IGNORE_MISSING | Ignore missing stack files when resolving environments |
|
|
595
|
+
| INTERACTIVE | Run one-off commands in an interactive shell |
|
|
593
596
|
| STACK | Stores the name of the current environment stack |
|
|
594
597
|
|
|
595
598
|
# Tests
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -34,6 +34,6 @@ Stacked environment variable management system.
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
__prog__ = "envstack"
|
|
37
|
-
__version__ = "0.9.
|
|
37
|
+
__version__ = "0.9.4"
|
|
38
38
|
|
|
39
39
|
from envstack.env import clear, init, revert, save # noqa: F401
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -37,6 +37,7 @@ import argparse
|
|
|
37
37
|
import re
|
|
38
38
|
import sys
|
|
39
39
|
import traceback
|
|
40
|
+
from typing import List
|
|
40
41
|
|
|
41
42
|
from envstack import __version__, config
|
|
42
43
|
from envstack.env import (
|
|
@@ -147,15 +148,14 @@ def parse_args():
|
|
|
147
148
|
action="version",
|
|
148
149
|
version=f"envstack {__version__}",
|
|
149
150
|
)
|
|
150
|
-
|
|
151
|
-
group.add_argument(
|
|
151
|
+
parser.add_argument(
|
|
152
152
|
"namespace",
|
|
153
153
|
metavar="STACK",
|
|
154
154
|
nargs="*",
|
|
155
155
|
default=[config.DEFAULT_NAMESPACE],
|
|
156
156
|
help="the environment stacks to use",
|
|
157
157
|
)
|
|
158
|
-
|
|
158
|
+
parser.add_argument(
|
|
159
159
|
"-b",
|
|
160
160
|
"--bare",
|
|
161
161
|
action="store_true",
|
|
@@ -221,6 +221,12 @@ def parse_args():
|
|
|
221
221
|
metavar="SCOPE",
|
|
222
222
|
help="search scope for environment stack files",
|
|
223
223
|
)
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"-u",
|
|
226
|
+
"--unresolved",
|
|
227
|
+
action="store_true",
|
|
228
|
+
help="dump unresolved environment variables to stdout",
|
|
229
|
+
)
|
|
224
230
|
parser.add_argument(
|
|
225
231
|
"-r",
|
|
226
232
|
"--resolve",
|
|
@@ -254,9 +260,20 @@ def parse_args():
|
|
|
254
260
|
return args, args_after_dash
|
|
255
261
|
|
|
256
262
|
|
|
263
|
+
def envshell(namespace: List[str] = None):
|
|
264
|
+
"""Run a shell in the given environment stack."""
|
|
265
|
+
from .envshell import EnvshellWrapper
|
|
266
|
+
|
|
267
|
+
print("\U0001F680 Launching envstack shell... CTRL+D to exit")
|
|
268
|
+
|
|
269
|
+
name = (namespace or [config.DEFAULT_NAMESPACE])[:]
|
|
270
|
+
shell = EnvshellWrapper(name)
|
|
271
|
+
return shell.launch()
|
|
272
|
+
|
|
273
|
+
|
|
257
274
|
def whichenv():
|
|
258
275
|
"""Entry point for the whichenv command line tool. Finds {VAR}s."""
|
|
259
|
-
from
|
|
276
|
+
from .util import findenv
|
|
260
277
|
|
|
261
278
|
if len(sys.argv) != 2:
|
|
262
279
|
print("Usage: whichenv [VAR]")
|
|
@@ -426,13 +443,16 @@ def main():
|
|
|
426
443
|
for source in env.sources:
|
|
427
444
|
print(source.path)
|
|
428
445
|
|
|
429
|
-
|
|
446
|
+
elif args.unresolved:
|
|
430
447
|
env = load_environ(
|
|
431
448
|
args.namespace, platform=args.platform, encrypt=args.encrypt
|
|
432
449
|
)
|
|
433
450
|
for k, v in sorted(env.items(), key=lambda x: str(x[0])):
|
|
434
451
|
print(f"{k}={v}")
|
|
435
452
|
|
|
453
|
+
else:
|
|
454
|
+
return envshell(args.namespace)
|
|
455
|
+
|
|
436
456
|
except KeyboardInterrupt:
|
|
437
457
|
print("Stopping...")
|
|
438
458
|
return 2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -43,6 +43,7 @@ from envstack.logger import log
|
|
|
43
43
|
|
|
44
44
|
# cryptography and _rust dependency may not be available everywhere
|
|
45
45
|
# ImportError: DLL load failed while importing _rust: Module not found.
|
|
46
|
+
Fernet = None
|
|
46
47
|
try:
|
|
47
48
|
import cryptography.exceptions
|
|
48
49
|
from cryptography.fernet import Fernet, InvalidToken
|
|
@@ -50,7 +51,6 @@ try:
|
|
|
50
51
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
51
52
|
except ImportError as err:
|
|
52
53
|
log.debug("cryptography module not available: %s", err)
|
|
53
|
-
Fernet = None
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
class Base64Encryptor(object):
|
|
@@ -78,7 +78,7 @@ class FernetEncryptor(object):
|
|
|
78
78
|
KEY_VAR_NAME = "ENVSTACK_FERNET_KEY"
|
|
79
79
|
|
|
80
80
|
def __init__(self, key: str = None, env: dict = os.environ):
|
|
81
|
-
if key:
|
|
81
|
+
if key and Fernet:
|
|
82
82
|
self.key = Fernet(key)
|
|
83
83
|
else:
|
|
84
84
|
self.key = self.get_key(env)
|
|
@@ -90,7 +90,8 @@ class FernetEncryptor(object):
|
|
|
90
90
|
key = Fernet.generate_key()
|
|
91
91
|
return key.decode()
|
|
92
92
|
else:
|
|
93
|
-
log.
|
|
93
|
+
log.debug("Fernet encryption not available")
|
|
94
|
+
return ""
|
|
94
95
|
|
|
95
96
|
def get_key(self, env: dict = os.environ):
|
|
96
97
|
"""Load the encryption key from the environment `env`.
|
|
@@ -99,7 +100,7 @@ class FernetEncryptor(object):
|
|
|
99
100
|
:return: encryption key.
|
|
100
101
|
"""
|
|
101
102
|
key = env.get(self.KEY_VAR_NAME)
|
|
102
|
-
if key:
|
|
103
|
+
if key and Fernet:
|
|
103
104
|
return Fernet(key)
|
|
104
105
|
return key
|
|
105
106
|
|
|
@@ -313,12 +314,11 @@ def generate_keys():
|
|
|
313
314
|
|
|
314
315
|
:returns: Dictionary containing Fernet and AES-GCM keys.
|
|
315
316
|
"""
|
|
316
|
-
from envstack.node import Base64Node
|
|
317
317
|
|
|
318
318
|
symmetric_key = AESGCMEncryptor.generate_key()
|
|
319
319
|
fernet_key = FernetEncryptor.generate_key()
|
|
320
320
|
|
|
321
321
|
return {
|
|
322
|
-
AESGCMEncryptor.KEY_VAR_NAME:
|
|
323
|
-
FernetEncryptor.KEY_VAR_NAME:
|
|
322
|
+
AESGCMEncryptor.KEY_VAR_NAME: symmetric_key,
|
|
323
|
+
FernetEncryptor.KEY_VAR_NAME: fernet_key,
|
|
324
324
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -36,6 +36,7 @@ Contains functions and classes for processing scoped .env files.
|
|
|
36
36
|
import os
|
|
37
37
|
import re
|
|
38
38
|
import string
|
|
39
|
+
from collections import defaultdict
|
|
39
40
|
from pathlib import Path
|
|
40
41
|
|
|
41
42
|
import yaml # noqa
|
|
@@ -77,7 +78,7 @@ class Source(object):
|
|
|
77
78
|
:param path: path to .env file.
|
|
78
79
|
"""
|
|
79
80
|
self.path = path
|
|
80
|
-
self.data =
|
|
81
|
+
self.data = defaultdict(dict)
|
|
81
82
|
|
|
82
83
|
def __eq__(self, other):
|
|
83
84
|
if not isinstance(other, Source):
|
|
@@ -126,7 +127,10 @@ class Source(object):
|
|
|
126
127
|
|
|
127
128
|
def write(self, filepath: str = None):
|
|
128
129
|
"""Writes the source data to the .env file."""
|
|
129
|
-
|
|
130
|
+
try:
|
|
131
|
+
util.dump_yaml(filepath or self.path, self.data)
|
|
132
|
+
except Exception as err:
|
|
133
|
+
logger.log.exception("Failed to write %s: %s", filepath or self.path, err)
|
|
130
134
|
|
|
131
135
|
|
|
132
136
|
class EnvVar(string.Template, str):
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# - Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# - Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# - Neither the name of the software nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software
|
|
17
|
+
# without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
#
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Contains envshell wrapper class.
|
|
34
|
+
|
|
35
|
+
Goal: drop the user into an interactive shell *without* sourcing their usual
|
|
36
|
+
shell rc files, so prompt vars (PS1/PROMPT) set by envstack can survive.
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- You can override the detected shell with ENVSTACK_SHELL.
|
|
40
|
+
Examples:
|
|
41
|
+
ENVSTACK_SHELL=/bin/zsh envstack --shell
|
|
42
|
+
ENVSTACK_SHELL=pwsh envstack --shell
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
import os
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from typing import List
|
|
48
|
+
|
|
49
|
+
from . import config
|
|
50
|
+
from .wrapper import Wrapper
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _basename(p: str) -> str:
|
|
54
|
+
"""Return the lowercase basename of a path, robustly."""
|
|
55
|
+
try:
|
|
56
|
+
return Path(p).name.lower()
|
|
57
|
+
except Exception:
|
|
58
|
+
return os.path.basename(p).lower()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _detect_shell_argv() -> List[str]:
|
|
62
|
+
"""
|
|
63
|
+
Return argv list for a *clean* interactive shell.
|
|
64
|
+
"""
|
|
65
|
+
shell = os.environ.get("ENVSTACK_SHELL", config.SHELL)
|
|
66
|
+
|
|
67
|
+
if os.name == "nt":
|
|
68
|
+
# Prefer COMSPEC if it looks like cmd.exe; otherwise allow pwsh/powershell.
|
|
69
|
+
comspec = os.environ.get("COMSPEC", "cmd.exe")
|
|
70
|
+
|
|
71
|
+
# If user explicitly asked for pwsh/powershell, honor it.
|
|
72
|
+
base = _basename(shell)
|
|
73
|
+
if base in ("pwsh", "pwsh.exe"):
|
|
74
|
+
return [shell, "-NoExit", "-NoProfile"]
|
|
75
|
+
if base in ("powershell", "powershell.exe"):
|
|
76
|
+
return [shell, "-NoExit", "-NoProfile"]
|
|
77
|
+
|
|
78
|
+
# Otherwise use cmd.exe, with /K to keep it open.
|
|
79
|
+
# (Even if config.SHELL returned "cmd", use COMSPEC so we get the real path.)
|
|
80
|
+
return [comspec, "/K"]
|
|
81
|
+
|
|
82
|
+
# POSIX shells
|
|
83
|
+
base = _basename(shell)
|
|
84
|
+
|
|
85
|
+
# bash: skip /etc/profile, ~/.bash_profile, ~/.bashrc, but stay interactive
|
|
86
|
+
if base == "bash":
|
|
87
|
+
return [shell, "--noprofile", "--norc", "-i"]
|
|
88
|
+
|
|
89
|
+
# zsh: -f skips zshrcs; -i for interactive
|
|
90
|
+
if base == "zsh":
|
|
91
|
+
return [shell, "-f", "-i"]
|
|
92
|
+
|
|
93
|
+
# tcsh/csh: -f skips rc; interactive by default when attached to a tty
|
|
94
|
+
if base in ("tcsh", "csh"):
|
|
95
|
+
return [shell, "-f"]
|
|
96
|
+
|
|
97
|
+
# fish: --no-config skips config.fish; interactive by default
|
|
98
|
+
if base == "fish":
|
|
99
|
+
return [shell, "--no-config"]
|
|
100
|
+
|
|
101
|
+
# Fallback: try interactive flag if common; otherwise just exec the shell
|
|
102
|
+
# (Most shells become interactive when connected to a tty anyway.)
|
|
103
|
+
return [shell, "-i"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class EnvshellWrapper(Wrapper):
|
|
107
|
+
"""A wrapper that spawns an interactive shell with the environment set."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, *args, **kwargs):
|
|
110
|
+
super(EnvshellWrapper, self).__init__(*args, **kwargs)
|
|
111
|
+
self.shell = False # exec the shell directly
|
|
112
|
+
|
|
113
|
+
def executable(self):
|
|
114
|
+
"""
|
|
115
|
+
Kept for interface compatibility. The actual argv is produced in
|
|
116
|
+
get_subprocess_command().
|
|
117
|
+
"""
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
def get_subprocess_command(self, env):
|
|
121
|
+
"""
|
|
122
|
+
Override to return argv list for subprocess.Popen(..., shell=False).
|
|
123
|
+
"""
|
|
124
|
+
return _detect_shell_argv()
|
|
125
|
+
|
|
126
|
+
def get_shell_prompt(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Return the environment variable that controls the shell prompt and its
|
|
129
|
+
desired value.
|
|
130
|
+
"""
|
|
131
|
+
if os.name == "nt":
|
|
132
|
+
return ("PROMPT", "$E[32m(${ENV:=${STACK}})$E[0m $P$G ")
|
|
133
|
+
else:
|
|
134
|
+
return ("PS1", "\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
|
|
135
|
+
|
|
136
|
+
def get_subprocess_env(self):
|
|
137
|
+
"""
|
|
138
|
+
Override to inject PS1/PROMPT if not already set.
|
|
139
|
+
"""
|
|
140
|
+
prompt_env, prompt_value = self.get_shell_prompt()
|
|
141
|
+
if prompt_env not in self.env:
|
|
142
|
+
self.env[prompt_env] = prompt_value
|
|
143
|
+
return super().get_subprocess_env()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -328,7 +328,7 @@ class CustomDumper(yaml.SafeDumper):
|
|
|
328
328
|
node.style = '"%s"' % node.value
|
|
329
329
|
elif node.value and node.value[-1] == ":":
|
|
330
330
|
node.style = '"%s"' % node.value
|
|
331
|
-
elif ": " in node.value:
|
|
331
|
+
elif ": " in str(node.value):
|
|
332
332
|
node.style = '"%s"' % node.value
|
|
333
333
|
|
|
334
334
|
def quote_vars(self, node: yaml.Node):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
3
|
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
@@ -604,17 +604,12 @@ def validate_yaml(file_path: str):
|
|
|
604
604
|
|
|
605
605
|
:param file_path: Path to the YAML file to validate.
|
|
606
606
|
"""
|
|
607
|
-
required_keys = {"all", "darwin", "linux", "windows"}
|
|
608
|
-
|
|
609
607
|
try:
|
|
610
608
|
with open(file_path, "r") as stream:
|
|
611
609
|
data = yaml.safe_load(stream.read())
|
|
612
610
|
# data = yaml.load(stream.read(), Loader=CustomLoader)
|
|
613
611
|
if not isinstance(data, dict):
|
|
614
612
|
raise yaml.YAMLError("invalid data structure")
|
|
615
|
-
missing_keys = required_keys - data.keys()
|
|
616
|
-
if missing_keys:
|
|
617
|
-
raise yaml.YAMLError(f"missing keys: {', '.join(sorted(missing_keys))}")
|
|
618
613
|
return data
|
|
619
614
|
except OSError as e:
|
|
620
615
|
print(e)
|