envstack 0.9.2__tar.gz → 0.9.3__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.
Files changed (30) hide show
  1. {envstack-0.9.2/lib/envstack.egg-info → envstack-0.9.3}/PKG-INFO +9 -7
  2. {envstack-0.9.2 → envstack-0.9.3}/README.md +8 -6
  3. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/__init__.py +1 -1
  4. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/cli.py +23 -4
  5. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/encrypt.py +7 -7
  6. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/env.py +6 -2
  7. envstack-0.9.3/lib/envstack/envshell.py +124 -0
  8. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/node.py +1 -1
  9. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/util.py +0 -5
  10. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/wrapper.py +1 -1
  11. {envstack-0.9.2 → envstack-0.9.3/lib/envstack.egg-info}/PKG-INFO +9 -7
  12. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/SOURCES.txt +1 -0
  13. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/entry_points.txt +1 -0
  14. {envstack-0.9.2 → envstack-0.9.3}/setup.py +2 -1
  15. {envstack-0.9.2 → envstack-0.9.3}/tests/test_cmds.py +42 -30
  16. {envstack-0.9.2 → envstack-0.9.3}/tests/test_env.py +9 -9
  17. {envstack-0.9.2 → envstack-0.9.3}/tests/test_node.py +1 -1
  18. {envstack-0.9.2 → envstack-0.9.3}/LICENSE +0 -0
  19. {envstack-0.9.2 → envstack-0.9.3}/dist.json +0 -0
  20. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/config.py +0 -0
  21. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/exceptions.py +0 -0
  22. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/logger.py +0 -0
  23. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack/path.py +0 -0
  24. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/dependency_links.txt +0 -0
  25. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/not-zip-safe +0 -0
  26. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/requires.txt +0 -0
  27. {envstack-0.9.2 → envstack-0.9.3}/lib/envstack.egg-info/top_level.txt +0 -0
  28. {envstack-0.9.2 → envstack-0.9.3}/setup.cfg +0 -0
  29. {envstack-0.9.2 → envstack-0.9.3}/tests/test_encrypt.py +0 -0
  30. {envstack-0.9.2 → envstack-0.9.3}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.9.2
3
+ Version: 0.9.3
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, add `keys` to the env stack:
376
+ To decrypt, run the command inside the `keys` environment again:
375
377
 
376
378
  ```bash
377
- $ envstack keys encrypted -r HELLO
379
+ $ ./keys.env -- envstack encrypted -r HELLO
378
380
  HELLO=world
379
381
  ```
380
382
 
381
- Or run the command inside the `keys` environment like this:
383
+ Or add `keys` to the env stack:
382
384
 
383
385
  ```bash
384
- $ ./keys.env -- envsatck encrypted -r HELLO
386
+ $ envstack keys encrypted -r HELLO
385
387
  HELLO=world
386
388
  ```
387
389
 
388
- Or include `keys` in environments to automatically decrypt:
390
+ Or automatically include `keys`:
389
391
 
390
392
  ```yaml
391
393
  include: [keys]
@@ -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, add `keys` to the env stack:
352
+ To decrypt, run the command inside the `keys` environment again:
351
353
 
352
354
  ```bash
353
- $ envstack keys encrypted -r HELLO
355
+ $ ./keys.env -- envstack encrypted -r HELLO
354
356
  HELLO=world
355
357
  ```
356
358
 
357
- Or run the command inside the `keys` environment like this:
359
+ Or add `keys` to the env stack:
358
360
 
359
361
  ```bash
360
- $ ./keys.env -- envsatck encrypted -r HELLO
362
+ $ envstack keys encrypted -r HELLO
361
363
  HELLO=world
362
364
  ```
363
365
 
364
- Or include `keys` in environments to automatically decrypt:
366
+ Or automatically include `keys`:
365
367
 
366
368
  ```yaml
367
369
  include: [keys]
@@ -34,6 +34,6 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.9.2"
37
+ __version__ = "0.9.3"
38
38
 
39
39
  from envstack.env import clear, init, revert, save # noqa: F401
@@ -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,20 +148,24 @@ def parse_args():
147
148
  action="version",
148
149
  version=f"envstack {__version__}",
149
150
  )
150
- group = parser.add_mutually_exclusive_group(required=False)
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
- group.add_argument(
158
+ parser.add_argument(
159
159
  "-b",
160
160
  "--bare",
161
161
  action="store_true",
162
162
  help="create a bare environment",
163
163
  )
164
+ parser.add_argument(
165
+ "--shell",
166
+ action="store_true",
167
+ help="drop into a shell with the environment loaded",
168
+ )
164
169
  encrypt_group = parser.add_argument_group("encryption options")
165
170
  encrypt_group.add_argument(
166
171
  "-e",
@@ -254,9 +259,20 @@ def parse_args():
254
259
  return args, args_after_dash
255
260
 
256
261
 
262
+ def envshell(namespace: List[str] = None):
263
+ """Run a shell in the given environment stack."""
264
+ from .envshell import EnvshellWrapper
265
+
266
+ print("\U0001F680 Launching envshell... CTRL+D to exit")
267
+
268
+ name = (namespace or [config.DEFAULT_NAMESPACE])[:]
269
+ envshell = EnvshellWrapper(name)
270
+ return envshell.launch()
271
+
272
+
257
273
  def whichenv():
258
274
  """Entry point for the whichenv command line tool. Finds {VAR}s."""
259
- from envstack.util import findenv
275
+ from .util import findenv
260
276
 
261
277
  if len(sys.argv) != 2:
262
278
  print("Usage: whichenv [VAR]")
@@ -276,6 +292,9 @@ def main():
276
292
  if command:
277
293
  return run_command(command, args.namespace)
278
294
 
295
+ elif args.shell:
296
+ return envshell(args.namespace)
297
+
279
298
  elif args.keygen:
280
299
  from envstack.encrypt import generate_keys
281
300
 
@@ -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.error("Fernet encryption not available")
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: Base64Node(symmetric_key),
323
- FernetEncryptor.KEY_VAR_NAME: Base64Node(fernet_key),
322
+ AESGCMEncryptor.KEY_VAR_NAME: symmetric_key,
323
+ FernetEncryptor.KEY_VAR_NAME: fernet_key,
324
324
  }
@@ -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
- util.dump_yaml(filepath or self.path, self.data)
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,124 @@
1
+ #!/usr/bin/env python
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()
@@ -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):
@@ -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)
@@ -128,7 +128,7 @@ class Wrapper(object):
128
128
  traceback.print_exc()
129
129
  exitcode = 1
130
130
  else:
131
- stdout, stderr = process.communicate()
131
+ stdout, stderr = process.communicate() # dead code, kept for reference
132
132
  while stdout and stderr:
133
133
  self.log.info(stdout)
134
134
  self.log.error(stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.9.2
3
+ Version: 0.9.3
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, add `keys` to the env stack:
376
+ To decrypt, run the command inside the `keys` environment again:
375
377
 
376
378
  ```bash
377
- $ envstack keys encrypted -r HELLO
379
+ $ ./keys.env -- envstack encrypted -r HELLO
378
380
  HELLO=world
379
381
  ```
380
382
 
381
- Or run the command inside the `keys` environment like this:
383
+ Or add `keys` to the env stack:
382
384
 
383
385
  ```bash
384
- $ ./keys.env -- envsatck encrypted -r HELLO
386
+ $ envstack keys encrypted -r HELLO
385
387
  HELLO=world
386
388
  ```
387
389
 
388
- Or include `keys` in environments to automatically decrypt:
390
+ Or automatically include `keys`:
389
391
 
390
392
  ```yaml
391
393
  include: [keys]
@@ -7,6 +7,7 @@ lib/envstack/cli.py
7
7
  lib/envstack/config.py
8
8
  lib/envstack/encrypt.py
9
9
  lib/envstack/env.py
10
+ lib/envstack/envshell.py
10
11
  lib/envstack/exceptions.py
11
12
  lib/envstack/logger.py
12
13
  lib/envstack/node.py
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
+ envshell = envstack.cli:envshell
2
3
  envstack = envstack.cli:main
3
4
  whichenv = envstack.cli:whichenv
@@ -40,7 +40,7 @@ with open(os.path.join(here, "README.md")) as f:
40
40
 
41
41
  setup(
42
42
  name="envstack",
43
- version="0.9.2",
43
+ version="0.9.3",
44
44
  description="Stacked environment variable management system",
45
45
  long_description=long_description,
46
46
  long_description_content_type="text/markdown",
@@ -69,6 +69,7 @@ setup(
69
69
  entry_points={
70
70
  "console_scripts": [
71
71
  "envstack=envstack.cli:main",
72
+ "envshell=envstack.cli:envshell",
72
73
  "whichenv=envstack.cli:whichenv",
73
74
  ],
74
75
  },
@@ -73,7 +73,7 @@ class TestUnresolved(unittest.TestCase):
73
73
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
74
74
  self.root = {
75
75
  "linux": "/mnt/pipe",
76
- "win32": "X:/pipe",
76
+ "win32": "//tools/pipe",
77
77
  "darwin": "/Volumes/pipe",
78
78
  }.get(sys.platform)
79
79
  os.environ["ENVPATH"] = envpath
@@ -86,6 +86,7 @@ ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
86
86
  HELLO=${HELLO:=world}
87
87
  LOG_LEVEL=${LOG_LEVEL:=INFO}
88
88
  PATH=${DEPLOY_ROOT}/bin:${PATH}
89
+ PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
89
90
  PYTHONPATH=${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
90
91
  ROOT=%s
91
92
  STACK=default
@@ -104,6 +105,7 @@ ENVPATH=${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
104
105
  HELLO=${HELLO:=world}
105
106
  LOG_LEVEL=DEBUG
106
107
  PATH=${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
108
+ PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
107
109
  PYTHONPATH=${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
108
110
  ROOT=%s
109
111
  STACK=dev
@@ -122,6 +124,7 @@ ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
122
124
  HELLO=${HELLO:=world}
123
125
  LOG_LEVEL=INFO
124
126
  PATH=${DEPLOY_ROOT}/bin:${PATH}
127
+ PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
125
128
  PYTHONPATH=${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
126
129
  ROOT=%s
127
130
  STACK=distman
@@ -140,6 +143,7 @@ ENVPATH=${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
140
143
  HELLO=${HELLO:=world}
141
144
  LOG_LEVEL=${LOG_LEVEL:=INFO}
142
145
  PATH=${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
146
+ PS1=\[\e[33m\](${STACK})\[\e[0m\] \w\$
143
147
  PYEXE=/usr/bin/python
144
148
  PYTHONPATH=${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
145
149
  ROOT=%s
@@ -163,6 +167,7 @@ INT=5
163
167
  LOG_LEVEL=${LOG_LEVEL:=INFO}
164
168
  NUMBER_LIST=[1, 2, 3]
165
169
  PATH=${DEPLOY_ROOT}/bin:${PATH}
170
+ PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
166
171
  PYTHONPATH=${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
167
172
  ROOT=${HOME}/.local/pipe
168
173
  STACK=thing
@@ -182,7 +187,7 @@ class TestEncrypt(unittest.TestCase):
182
187
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
183
188
  self.root = {
184
189
  "linux": "/mnt/pipe",
185
- "win32": "X:/pipe",
190
+ "win32": "//tools/pipe",
186
191
  "darwin": "/Volumes/pipe",
187
192
  }.get(sys.platform)
188
193
  os.environ["ENVPATH"] = envpath
@@ -203,6 +208,7 @@ ENVPATH=JHtERVBMT1lfUk9PVH0vZW52OiR7RU5WUEFUSH0=
203
208
  HELLO=JHtIRUxMTzo9d29ybGR9
204
209
  LOG_LEVEL=JHtMT0dfTEVWRUw6PUlORk99
205
210
  PATH=JHtERVBMT1lfUk9PVH0vYmluOiR7UEFUSH0=
211
+ PS1=XFtcZVszMm1cXSgke0VOVn0pXFtcZVswbVxdIFx3XCQg
206
212
  PYTHONPATH=JHtERVBMT1lfUk9PVH0vbGliL3B5dGhvbjoke1BZVEhPTlBBVEh9
207
213
  ROOT=L21udC9waXBl
208
214
  STACK=ZGVmYXVsdA==
@@ -251,6 +257,7 @@ ENVPATH=JHtST09UfS9kZXYvZW52OiR7Uk9PVH0vcHJvZC9lbnY6JHtFTlZQQVRIfQ==
251
257
  HELLO=JHtIRUxMTzo9d29ybGR9
252
258
  LOG_LEVEL=REVCVUc=
253
259
  PATH=JHtST09UfS9kZXYvYmluOiR7Uk9PVH0vcHJvZC9iaW46JHtQQVRIfQ==
260
+ PS1=XFtcZVszMm1cXSgke0VOVn0pXFtcZVswbVxdIFx3XCQg
254
261
  PYTHONPATH=JHtST09UfS9kZXYvbGliL3B5dGhvbjoke1JPT1R9L3Byb2QvbGliL3B5dGhvbjoke1BZVEhPTlBBVEh9
255
262
  ROOT=L21udC9waXBl
256
263
  STACK=ZGV2
@@ -277,7 +284,7 @@ class TestResolved(unittest.TestCase):
277
284
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
278
285
  self.root = {
279
286
  "linux": "/mnt/pipe",
280
- "win32": "X:/pipe",
287
+ "win32": "//tools/pipe",
281
288
  "darwin": "/Volumes/pipe",
282
289
  }.get(sys.platform)
283
290
  os.environ["ENVPATH"] = envpath
@@ -321,14 +328,14 @@ STACK=distman
321
328
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
322
329
  self.assertEqual(output, expected_output)
323
330
 
324
- def test_test(self):
325
- expected_output = f"""DEPLOY_ROOT={self.root}/test
331
+ def test_project(self):
332
+ expected_output = f"""DEPLOY_ROOT={self.root}/project
326
333
  HELLO=world
327
334
  ROOT={self.root}
328
- STACK=test
335
+ STACK=project
329
336
  """
330
337
  command = (
331
- "ENV=blah ROOT=/var/tmp %s test -r DEPLOY_ROOT HELLO ROOT STACK"
338
+ "ENV=blah ROOT=/var/tmp %s project -r DEPLOY_ROOT HELLO ROOT STACK"
332
339
  % self.envstack_bin
333
340
  )
334
341
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
@@ -341,7 +348,7 @@ ROOT={self.root}
341
348
  STACK=foobar
342
349
  """
343
350
  command = (
344
- "ENV=blah ROOT=/var/tmp %s test foobar -r DEPLOY_ROOT HELLO ROOT STACK"
351
+ "ENV=blah ROOT=/var/tmp %s project foobar -r DEPLOY_ROOT HELLO ROOT STACK"
345
352
  % self.envstack_bin
346
353
  )
347
354
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
@@ -372,7 +379,7 @@ class TestBake(unittest.TestCase):
372
379
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
373
380
  self.root = {
374
381
  "linux": "/mnt/pipe",
375
- "win32": "X:/pipe",
382
+ "win32": "//tools/pipe",
376
383
  "darwin": "/Volumes/pipe",
377
384
  }.get(sys.platform)
378
385
  os.environ["ENVPATH"] = envpath
@@ -401,13 +408,16 @@ all: &all
401
408
  PYTHONPATH: ${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
402
409
  darwin:
403
410
  <<: *all
411
+ PS1: \[\e[32m\](${ENV})\[\e[0m\] \w\$
404
412
  ROOT: /Volumes/pipe
405
413
  linux:
406
414
  <<: *all
415
+ PS1: \[\e[32m\](${ENV})\[\e[0m\] \w\$
407
416
  ROOT: /mnt/pipe
408
417
  windows:
409
418
  <<: *all
410
- ROOT: X:/pipe
419
+ PROMPT: $E[32m(${ENV})$E[0m $P$G
420
+ ROOT: //tools/pipe
411
421
  """
412
422
  output = subprocess.check_output(
413
423
  command,
@@ -418,7 +428,7 @@ windows:
418
428
 
419
429
  def test_dev(self):
420
430
  """Tests baking the dev stack."""
421
- command = make_command(self.envstack_bin, self.filename, "dev", "-d 1")
431
+ command = make_command(self.envstack_bin, self.filename, "dev", "-d", "1")
422
432
  expected_output = """#!/usr/bin/env envstack
423
433
  include: [default]
424
434
  all: &all
@@ -489,13 +499,16 @@ all: &all
489
499
  PYTHONPATH: !encrypt JHtERVBMT1lfUk9PVH0vbGliL3B5dGhvbjoke1BZVEhPTlBBVEh9
490
500
  darwin:
491
501
  <<: *all
502
+ PS1: !encrypt XFtcZVszMm1cXSgke0VOVn0pXFtcZVswbVxdIFx3XCQg
492
503
  ROOT: !encrypt L1ZvbHVtZXMvcGlwZQ==
493
504
  linux:
494
505
  <<: *all
506
+ PS1: !encrypt XFtcZVszMm1cXSgke0VOVn0pXFtcZVswbVxdIFx3XCQg
495
507
  ROOT: !encrypt L21udC9waXBl
496
508
  windows:
497
509
  <<: *all
498
- ROOT: !encrypt WDovcGlwZQ==
510
+ PROMPT: !encrypt JEVbMzJtKCR7RU5WfSkkRVswbSAkUCRH
511
+ ROOT: !encrypt Ly90b29scy9waXBl
499
512
  """
500
513
  output = subprocess.check_output(
501
514
  command,
@@ -568,7 +581,7 @@ class TestCommands(unittest.TestCase):
568
581
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
569
582
  self.root = {
570
583
  "linux": "/mnt/pipe",
571
- "win32": "X:/pipe",
584
+ "win32": "//tools/pipe",
572
585
  "darwin": "/Volumes/pipe",
573
586
  }.get(sys.platform)
574
587
  os.environ["ENVPATH"] = envpath
@@ -602,18 +615,18 @@ class TestCommands(unittest.TestCase):
602
615
  )
603
616
  self.assertEqual(output, expected_output)
604
617
 
605
- def test_test_echo_deploy_root(self):
606
- """Tests the test stack with an echo command."""
607
- command = "%s test -- echo {DEPLOY_ROOT}" % self.envstack_bin
608
- expected_output = f"{self.root}/test\n"
618
+ def test_project_echo_deploy_root(self):
619
+ """Tests the project stack with an echo command."""
620
+ command = "%s project -- echo {DEPLOY_ROOT}" % self.envstack_bin
621
+ expected_output = f"{self.root}/project\n"
609
622
  output = subprocess.check_output(
610
623
  command, start_new_session=True, shell=True, universal_newlines=True
611
624
  )
612
625
  self.assertEqual(output, expected_output)
613
626
 
614
- def test_test_echo_deploy_root(self):
615
- """Tests the test stack with an echo command."""
616
- command = "%s test foobar -- echo {DEPLOY_ROOT}" % self.envstack_bin
627
+ def test_project_echo_deploy_root_foobar(self):
628
+ """Tests the project stack with an echo command."""
629
+ command = "%s project foobar -- echo {DEPLOY_ROOT}" % self.envstack_bin
617
630
  expected_output = f"{self.root}/foobar\n"
618
631
  output = subprocess.check_output(
619
632
  command, start_new_session=True, shell=True, universal_newlines=True
@@ -632,7 +645,7 @@ class TestSet(unittest.TestCase):
632
645
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
633
646
  self.root = {
634
647
  "linux": "/mnt/pipe",
635
- "win32": "X:/pipe",
648
+ "win32": "//tools/pipe",
636
649
  "darwin": "/Volumes/pipe",
637
650
  }.get(sys.platform)
638
651
  os.environ["ENVPATH"] = envpath
@@ -799,7 +812,7 @@ class TestDistman(unittest.TestCase):
799
812
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
800
813
  self.root = {
801
814
  "linux": "/mnt/pipe",
802
- "win32": "X:/pipe",
815
+ "win32": "//tools/pipe",
803
816
  "darwin": "/Volumes/pipe",
804
817
  }.get(sys.platform)
805
818
  os.environ["ENVPATH"] = envpath
@@ -823,15 +836,15 @@ class TestDistman(unittest.TestCase):
823
836
  self.assertEqual(output, expected_output)
824
837
 
825
838
  def test_test_deploy_root(self):
826
- command = "ENV=invalid %s test -- %s" % (self.envstack_bin, self.python_cmd)
827
- expected_output = f"{self.root}/test\n"
839
+ command = "ENV=invalid %s project -- %s" % (self.envstack_bin, self.python_cmd)
840
+ expected_output = f"{self.root}/project\n"
828
841
  output = subprocess.check_output(
829
842
  command, start_new_session=True, shell=True, universal_newlines=True
830
843
  )
831
844
  self.assertEqual(output, expected_output)
832
845
 
833
846
  def test_foobar_deploy_root(self):
834
- command = "ENV=invalid %s test foobar -- %s" % (
847
+ command = "ENV=invalid %s project foobar -- %s" % (
835
848
  self.envstack_bin,
836
849
  self.python_cmd,
837
850
  )
@@ -922,7 +935,7 @@ class TestIssues(unittest.TestCase):
922
935
  env/dev.env:
923
936
  ENVPATH: ${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
924
937
 
925
- env/test.env:
938
+ env/project.env:
926
939
  ENVPATH: ${ROOT}/${STACK}/env:${ROOT}/prod/env
927
940
  """
928
941
  from envstack.env import Env
@@ -950,11 +963,10 @@ class TestIssues(unittest.TestCase):
950
963
  )
951
964
  self.assertEqual(output, expected_output)
952
965
 
953
- # test.env does use STACK in ENVPATH and should include our test env
954
- command = "%s test test_issue_55 --sources" % self.envstack_bin
966
+ # project.env does use STACK in ENVPATH and should include our test env
967
+ command = "%s project test_issue_55 --sources" % self.envstack_bin
955
968
  expected_output = f"""{self.root}/prod/env/default.env
956
- {self.root}/prod/env/dev.env
957
- {self.root}/prod/env/test.env
969
+ {self.root}/prod/env/project.env
958
970
  {self.root}/test_issue_55/env/test_issue_55.env
959
971
  """
960
972
  output = subprocess.check_output(
@@ -176,7 +176,7 @@ class TestEnv(unittest.TestCase):
176
176
  env = load_environ("thing")
177
177
  baked = env.bake()
178
178
  for k, v in env.items():
179
- if k == "STACK":
179
+ if k == "STACK" or k == "PS1": # skip
180
180
  continue
181
181
  self.assertEqual(baked[k], v)
182
182
 
@@ -189,7 +189,7 @@ class TestEnv(unittest.TestCase):
189
189
  env1.write(self.filename, depth=0)
190
190
  env2 = load_environ(self.filename)
191
191
  for k, v in env1.items():
192
- if k == "STACK":
192
+ if k == "STACK" or k == "PS1": # skip
193
193
  continue
194
194
  self.assertEqual(env2[k], v)
195
195
 
@@ -342,8 +342,8 @@ class TestInit(unittest.TestCase):
342
342
  self.assertEqual(diffs["removed"], {})
343
343
  self.assertEqual(diffs["unchanged"], original_env)
344
344
 
345
- def test_init_zzz_custom(self):
346
- """Tests init with custom test stack."""
345
+ def test_init_zzz_project(self):
346
+ """Tests init with custom project stack."""
347
347
  envpath = os.path.join(os.path.dirname(__file__), "..", "env")
348
348
  os.environ["ENVPATH"] = envpath
349
349
  os.environ["HELLO"] = "goodbye"
@@ -351,7 +351,7 @@ class TestInit(unittest.TestCase):
351
351
  original_env = os.environ.copy()
352
352
  sys_path = sys.path.copy()
353
353
 
354
- envstack.init("test", "custom", ignore_missing=True)
354
+ envstack.init("project", "custom", ignore_missing=True)
355
355
  self.assertEqual(os.getenv("ENV"), "custom")
356
356
  self.assertEqual(os.getenv("STACK"), "custom")
357
357
  self.assertEqual(os.getenv("DEPLOY_ROOT"), f"{self.root}/custom")
@@ -667,7 +667,7 @@ class TestBakeEnviron(unittest.TestCase):
667
667
 
668
668
  # compare the values
669
669
  for key, value in env.items():
670
- if key == "STACK": # skip the stack name
670
+ if key == "STACK" or key == "PS1": # skip
671
671
  continue
672
672
  self.assertEqual(baked[key], value)
673
673
 
@@ -695,7 +695,7 @@ class TestBakeEnviron(unittest.TestCase):
695
695
 
696
696
  # compare the values
697
697
  for key, value in env.items():
698
- if key == "STACK":
698
+ if key == "STACK" or key == "PS1": # skip
699
699
  continue
700
700
  self.assertEqual(baked2_reloaded[key], value)
701
701
 
@@ -1062,9 +1062,9 @@ class TestIssues(unittest.TestCase):
1062
1062
  # FIXME: why is this necessary? (think it's caching seen stacks)
1063
1063
  envstack.revert()
1064
1064
 
1065
- # load our test env file by loading the "test" env first, which
1065
+ # load our project env file by loading the "project" env first, which
1066
1066
  # does use STACK in ENVPATH
1067
- env2 = load_environ(["test", "test_issue_55"])
1067
+ env2 = load_environ(["project", "test_issue_55"])
1068
1068
 
1069
1069
  # last env file should be our test env file
1070
1070
  self.assertEqual(str(env2.sources[-1].path), test_env_file)
@@ -172,7 +172,7 @@ class TestTemplate(unittest.TestCase):
172
172
  class TestCustomLoader(unittest.TestCase):
173
173
  def test_construct_mapping(self):
174
174
  """test the CustomLoader construct_mapping method"""
175
- envfile = os.path.join(envpath, "test.env")
175
+ envfile = os.path.join(envpath, "project.env")
176
176
  loader = CustomLoader(open(envfile, "r"))
177
177
  node = yaml.MappingNode("tag", [])
178
178
  mapping = loader.construct_mapping(node)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes