envstack 0.8.2__tar.gz → 0.8.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.
Files changed (29) hide show
  1. {envstack-0.8.2/lib/envstack.egg-info → envstack-0.8.4}/PKG-INFO +53 -2
  2. {envstack-0.8.2 → envstack-0.8.4}/README.md +52 -1
  3. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/__init__.py +1 -1
  4. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/cli.py +47 -22
  5. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/env.py +96 -46
  6. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/util.py +26 -34
  7. {envstack-0.8.2 → envstack-0.8.4/lib/envstack.egg-info}/PKG-INFO +53 -2
  8. {envstack-0.8.2 → envstack-0.8.4}/setup.py +1 -1
  9. {envstack-0.8.2 → envstack-0.8.4}/tests/test_cmds.py +126 -5
  10. {envstack-0.8.2 → envstack-0.8.4}/tests/test_env.py +234 -22
  11. {envstack-0.8.2 → envstack-0.8.4}/tests/test_util.py +71 -8
  12. {envstack-0.8.2 → envstack-0.8.4}/LICENSE +0 -0
  13. {envstack-0.8.2 → envstack-0.8.4}/dist.json +0 -0
  14. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/config.py +0 -0
  15. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/encrypt.py +0 -0
  16. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/exceptions.py +0 -0
  17. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/logger.py +0 -0
  18. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/node.py +0 -0
  19. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/path.py +0 -0
  20. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack/wrapper.py +0 -0
  21. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/SOURCES.txt +0 -0
  22. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/dependency_links.txt +0 -0
  23. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/entry_points.txt +0 -0
  24. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/not-zip-safe +0 -0
  25. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/requires.txt +0 -0
  26. {envstack-0.8.2 → envstack-0.8.4}/lib/envstack.egg-info/top_level.txt +0 -0
  27. {envstack-0.8.2 → envstack-0.8.4}/setup.cfg +0 -0
  28. {envstack-0.8.2 → envstack-0.8.4}/tests/test_encrypt.py +0 -0
  29. {envstack-0.8.2 → envstack-0.8.4}/tests/test_node.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -169,8 +169,8 @@ allows you to override the value:
169
169
  | Value | Description |
170
170
  |---------------------|-------------|
171
171
  | value | 'value' |
172
- | ${VAR:-default} | os.environ.get('VAR', 'default') |
173
172
  | ${VAR:=default} | VAR = VAR or 'default' |
173
+ | ${VAR:-default} | os.environ.get('VAR', 'default') |
174
174
  | ${VAR:?error message} | if not VAR: raise ValueError() |
175
175
 
176
176
  Without the expansion modifier, values are set and do not change (but can be
@@ -208,6 +208,37 @@ $ HELLO=goodbye envstack -- echo {HELLO}
208
208
  goodbye
209
209
  ```
210
210
 
211
+ #### Using the command-line
212
+
213
+ Here we can set values using the `envstack` command:
214
+
215
+ ```bash
216
+ $ envstack --set HELLO:world
217
+ HELLO: world
218
+ ```
219
+
220
+ We can also encrypt the values automatically (base64 by default):
221
+
222
+ ```bash
223
+ $ envstack -s HELLO:world -e
224
+ HELLO: d29ybGQ=
225
+ ```
226
+
227
+ Add more variables (note that `$` needs to be escaped in bash or else it will
228
+ be evaluated immediately):
229
+
230
+ ```bash
231
+ $ envstack -s HELLO:world VAR:\${HELLO}
232
+ HELLO: world
233
+ VAR: ${HELLO}
234
+ ```
235
+
236
+ To write out to a file use the `-o` option:
237
+
238
+ ```bash
239
+ $ envstack -s HELLO:world -o hello.env
240
+ ```
241
+
211
242
  ## Creating Stacks
212
243
 
213
244
  Several example or starter stacks are available in the [env folder of the
@@ -235,6 +266,13 @@ windows:
235
266
  <<: *all
236
267
  ```
237
268
 
269
+ Or using Python:
270
+
271
+ ```python
272
+ >>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
273
+ >>> env.write("foobar.env")
274
+ ```
275
+
238
276
  Get the resolved environment for the `foobar` stack:
239
277
 
240
278
  ```bash
@@ -277,6 +315,19 @@ BIZ=foo
277
315
  FOO=foo
278
316
  ```
279
317
 
318
+ Here is an example using nested variable expansion:
319
+
320
+ ```yaml
321
+ FOO: ${BIZ:=${BAR:=${BAZ:=baz}}}
322
+ ```
323
+
324
+ Resolves to:
325
+
326
+ ```bash
327
+ $ envstack -r
328
+ FOO=baz
329
+ ```
330
+
280
331
  #### Includes
281
332
 
282
333
  Environment stack files can include other namespaced environments (you should
@@ -145,8 +145,8 @@ allows you to override the value:
145
145
  | Value | Description |
146
146
  |---------------------|-------------|
147
147
  | value | 'value' |
148
- | ${VAR:-default} | os.environ.get('VAR', 'default') |
149
148
  | ${VAR:=default} | VAR = VAR or 'default' |
149
+ | ${VAR:-default} | os.environ.get('VAR', 'default') |
150
150
  | ${VAR:?error message} | if not VAR: raise ValueError() |
151
151
 
152
152
  Without the expansion modifier, values are set and do not change (but can be
@@ -184,6 +184,37 @@ $ HELLO=goodbye envstack -- echo {HELLO}
184
184
  goodbye
185
185
  ```
186
186
 
187
+ #### Using the command-line
188
+
189
+ Here we can set values using the `envstack` command:
190
+
191
+ ```bash
192
+ $ envstack --set HELLO:world
193
+ HELLO: world
194
+ ```
195
+
196
+ We can also encrypt the values automatically (base64 by default):
197
+
198
+ ```bash
199
+ $ envstack -s HELLO:world -e
200
+ HELLO: d29ybGQ=
201
+ ```
202
+
203
+ Add more variables (note that `$` needs to be escaped in bash or else it will
204
+ be evaluated immediately):
205
+
206
+ ```bash
207
+ $ envstack -s HELLO:world VAR:\${HELLO}
208
+ HELLO: world
209
+ VAR: ${HELLO}
210
+ ```
211
+
212
+ To write out to a file use the `-o` option:
213
+
214
+ ```bash
215
+ $ envstack -s HELLO:world -o hello.env
216
+ ```
217
+
187
218
  ## Creating Stacks
188
219
 
189
220
  Several example or starter stacks are available in the [env folder of the
@@ -211,6 +242,13 @@ windows:
211
242
  <<: *all
212
243
  ```
213
244
 
245
+ Or using Python:
246
+
247
+ ```python
248
+ >>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
249
+ >>> env.write("foobar.env")
250
+ ```
251
+
214
252
  Get the resolved environment for the `foobar` stack:
215
253
 
216
254
  ```bash
@@ -253,6 +291,19 @@ BIZ=foo
253
291
  FOO=foo
254
292
  ```
255
293
 
294
+ Here is an example using nested variable expansion:
295
+
296
+ ```yaml
297
+ FOO: ${BIZ:=${BAR:=${BAZ:=baz}}}
298
+ ```
299
+
300
+ Resolves to:
301
+
302
+ ```bash
303
+ $ envstack -r
304
+ FOO=baz
305
+ ```
306
+
256
307
  #### Includes
257
308
 
258
309
  Environment stack files can include other namespaced environments (you should
@@ -34,6 +34,6 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.8.2"
37
+ __version__ = "0.8.4"
38
38
 
39
39
  from envstack.env import clear, init, revert, save # noqa: F401
@@ -40,7 +40,9 @@ import traceback
40
40
  from envstack import __version__, config
41
41
  from envstack.env import (
42
42
  bake_environ,
43
- clear,
43
+ Env,
44
+ encrypt_environ,
45
+ export_env_to_shell,
44
46
  export,
45
47
  load_environ,
46
48
  resolve_environ,
@@ -83,12 +85,25 @@ def parse_args():
83
85
  default=[config.DEFAULT_NAMESPACE],
84
86
  help="the environment stacks to use (default '%s')" % config.DEFAULT_NAMESPACE,
85
87
  )
88
+ encrypt_group = parser.add_argument_group("encryption options")
89
+ encrypt_group.add_argument(
90
+ "-e",
91
+ "--encrypt",
92
+ action="store_true",
93
+ help="encrypt environment values",
94
+ )
95
+ encrypt_group.add_argument(
96
+ "--keygen",
97
+ action="store_true",
98
+ help="generate encryption keys",
99
+ )
100
+ parser.add_argument_group(encrypt_group)
86
101
  bake_group = parser.add_argument_group("bake options")
87
102
  bake_group.add_argument(
88
103
  "-o",
89
104
  "--out",
90
105
  metavar="FILENAME",
91
- help="write the env stack to a new stack file",
106
+ help="save the environment to an env file",
92
107
  )
93
108
  bake_group.add_argument(
94
109
  "--depth",
@@ -96,27 +111,19 @@ def parse_args():
96
111
  default=0,
97
112
  help="depth of environment stack to bake",
98
113
  )
99
- bake_group.add_argument(
100
- "--keygen",
101
- action="store_true",
102
- help="generate encryption keys",
103
- )
104
- bake_group.add_argument(
105
- "--encrypt",
106
- action="store_true",
107
- help="encrypt the baked environment values",
108
- )
109
114
  parser.add_argument_group(bake_group)
110
- parser.add_argument(
115
+ export_group = parser.add_argument_group("export options")
116
+ export_group.add_argument(
111
117
  "--clear",
112
118
  action="store_true",
113
119
  help="generate unset commands for %s" % config.SHELL,
114
120
  )
115
- parser.add_argument(
121
+ export_group.add_argument(
116
122
  "--export",
117
123
  action="store_true",
118
124
  help="generate export commands for %s" % config.SHELL,
119
125
  )
126
+ parser.add_argument_group(export_group)
120
127
  parser.add_argument(
121
128
  "-p",
122
129
  "--platform",
@@ -124,6 +131,13 @@ def parse_args():
124
131
  metavar="PLATFORM",
125
132
  help="platform to resolve variables for (linux, darwin, windows)",
126
133
  )
134
+ parser.add_argument(
135
+ "-s",
136
+ "--set",
137
+ nargs="*",
138
+ metavar="VAR:VALUE",
139
+ help="set a key:value pair in the environment",
140
+ )
127
141
  parser.add_argument(
128
142
  "--scope",
129
143
  metavar="SCOPE",
@@ -179,19 +193,28 @@ def main():
179
193
  elif args.keygen:
180
194
  from envstack.encrypt import generate_keys
181
195
 
182
- keys = generate_keys()
196
+ data = generate_keys()
183
197
 
184
198
  if args.export:
185
- from envstack.env import export_env_to_shell
186
-
187
- print(export_env_to_shell(keys))
199
+ print(export_env_to_shell(data))
188
200
  elif args.out:
189
- from envstack.util import dump_yaml
201
+ Env(data).write(args.out)
202
+ else:
203
+ for key, value in data.items():
204
+ print(f"{key}={value}")
205
+
206
+ elif args.set:
207
+ data = dict(kv.split(":", 1) for kv in args.set)
190
208
 
191
- dump_yaml(file_path=args.out, data=keys)
209
+ if args.encrypt:
210
+ data = encrypt_environ(data, encrypt=(not args.out))
211
+ if args.export:
212
+ print(export_env_to_shell(data))
213
+ elif args.out:
214
+ Env(data).write(args.out)
192
215
  else:
193
- for key, value in keys.items():
194
- print(f"{key}: {value}")
216
+ for key, value in data.items():
217
+ print(f"{key}={value}")
195
218
 
196
219
  elif args.out:
197
220
  bake_environ(
@@ -223,6 +246,8 @@ def main():
223
246
  print(source.path)
224
247
 
225
248
  elif args.clear:
249
+ from envstack.env import clear
250
+
226
251
  print(clear(args.namespace, config.SHELL))
227
252
 
228
253
  elif args.export:
@@ -89,7 +89,7 @@ class Source(object):
89
89
  return hash(self.__repr__())
90
90
 
91
91
  def __repr__(self):
92
- return f'<Source "{self.path}">'
92
+ return f"<Source '{self.path}'>"
93
93
 
94
94
  def __str__(self):
95
95
  return str(self.path)
@@ -294,6 +294,88 @@ class Env(dict):
294
294
  """
295
295
  self.scope = path
296
296
 
297
+ def bake(self, filename: str = None, depth: int = 0, encrypt: bool = False):
298
+ """Bakes an environment with multiple sources into a single environment
299
+ and writes to a new env file.
300
+
301
+ >>> env = load_environ(stack_name)
302
+ >>> env.bake("baked.env")
303
+
304
+ :param filename: path to save the baked environment.
305
+ :param depth: depth of source files to incldue (default: all).
306
+ :param encrypt: encrypt the values.
307
+ :returns: baked environment.
308
+ """
309
+ # get the sources for the given environment
310
+ sources = self.sources
311
+
312
+ # look for encryption keys in the environment
313
+ os.environ.update(get_keys_from_env(self))
314
+
315
+ # create a baked source
316
+ baked = Source(filename)
317
+
318
+ def get_node_class(value):
319
+ """Returns the node class to use for a given value."""
320
+ if encrypt:
321
+ if type(value) in custom_node_types:
322
+ return value.__class__
323
+ else:
324
+ return EncryptedNode
325
+ return value.__class__
326
+
327
+ # merge the sources into the outfile
328
+ for source in sources[-depth:]:
329
+ for key, value in source.data.items():
330
+ if isinstance(value, dict):
331
+ for k, v in value.items():
332
+ node_class = get_node_class(v)
333
+ baked.data.setdefault(key, {})[k] = node_class(v)
334
+ else:
335
+ node_class = get_node_class(value)
336
+ baked.data[key] = node_class(value)
337
+
338
+ # clear includes if environment stack is fully baked
339
+ if depth <= 0:
340
+ baked.data["include"] = []
341
+
342
+ # write the baked environment to the file
343
+ if filename:
344
+ baked.write()
345
+
346
+ # create the baked environment from the baked source
347
+ baked_env = Env()
348
+ baked_env.load_source(baked)
349
+
350
+ return baked_env
351
+
352
+ def write(self, filename: str = None):
353
+ """Writes the environment to an env file.
354
+
355
+ >>> env = Env({"FOO": "${BAR}", "BAR": "bar"})
356
+ >>> env.write("foo.env")
357
+
358
+ To encrypt values, use EncryptedNode:
359
+
360
+ >>> env = Env({"FOO": "${BAR}", "BAR": EncryptedNode("bar")})
361
+ >>> env.write("encrypted.env")
362
+
363
+ :param filename: path to save the baked environment.
364
+ :returns: Source object.
365
+ """
366
+ # the environment was loaded from one or more sources
367
+ if self.sources:
368
+ baked = self.bake(filename)
369
+ return baked.sources[0]
370
+
371
+ # the environment was created from scratch
372
+ else:
373
+ source = Source(filename)
374
+ for k, v in self.items():
375
+ source.data[k] = v
376
+ source.write()
377
+ return source
378
+
297
379
 
298
380
  def clear_file_cache():
299
381
  """Clears global file cache."""
@@ -664,55 +746,21 @@ def bake_environ(
664
746
  :param encrypt: encrypt the values.
665
747
  :returns: baked environment.
666
748
  """
667
- # load the envrinment for the given stack and get list of sources
668
- env = load_environ(name, scope=scope)
669
- sources = env.sources
670
-
671
- # resolve internal environment so that encryption keys are found
672
- # (encryptors looks in os.environ by default)
673
- os.environ.update(util.encode(resolve_environ(env)))
749
+ return load_environ(name, scope=scope).bake(filename, depth, encrypt)
674
750
 
675
- # create a baked source
676
- baked = Source(filename)
677
751
 
678
- def get_node_class(value):
679
- """Returns the node class to use for a given value."""
680
- if encrypt:
681
- if type(value) in custom_node_types:
682
- return value.__class__
683
- else:
684
- return EncryptedNode
685
- return value.__class__
686
-
687
- # merge the sources into the outfile
688
- for source in sources[-depth:]:
689
- for key, value in source.data.items():
690
- if isinstance(value, dict):
691
- for k, v in value.items():
692
- node_class = get_node_class(v)
693
- baked.data.setdefault(key, {})[k] = node_class(v)
694
- else:
695
- node_class = get_node_class(value)
696
- baked.data[key] = node_class(value)
697
-
698
- # clear includes if environment stack is fully baked
699
- if depth <= 0:
700
- baked.data["include"] = []
701
-
702
- # write the baked environment to the file
703
- if filename:
704
- baked.write()
705
-
706
- # create the baked environment
707
- baked_env = Env()
708
- baked_env.update(baked.load())
752
+ def encrypt_environ(
753
+ env: dict, node_class: BaseNode = EncryptedNode, encrypt: bool = True
754
+ ):
755
+ """Encrypts all values in a given environment, returning a new environment.
756
+ Looks for encryption keys in the environment.
709
757
 
710
- return baked_env
758
+ Python:
711
759
 
760
+ >>> env = {"FOO": "bar"}
761
+ >>> env = envstack.encrypt_environ(env)
712
762
 
713
- def encrypt_environ(env: dict, node_class: BaseNode = EncryptedNode):
714
- """Encrypts all values in a given environment, returning a new environment.
715
- Looks for encryption keys in the environment.
763
+ Command line:
716
764
 
717
765
  $ envstack [STACK] --encrypt
718
766
 
@@ -720,6 +768,7 @@ def encrypt_environ(env: dict, node_class: BaseNode = EncryptedNode):
720
768
  :param node_class: node class to use for encryption.
721
769
  Defaults to EncryptedNode, which looks for encryption keys in the
722
770
  environment to determine the encryption method.
771
+ :param encrypt: pre-encrypt the values.
723
772
  :returns: encrypted environment.
724
773
  """
725
774
  # stores the encrypted environment
@@ -736,7 +785,8 @@ def encrypt_environ(env: dict, node_class: BaseNode = EncryptedNode):
736
785
  if type(v) not in custom_node_types:
737
786
  # TODO: use to_yaml() method to serialize instead?
738
787
  node = node_class(v)
739
- node.value = node.encryptor(env=resolved_env).encrypt(str(v))
788
+ if encrypt:
789
+ node.value = node.encryptor(env=resolved_env).encrypt(str(v))
740
790
  encrypted_env[k] = node
741
791
  else:
742
792
  encrypted_env[k] = v
@@ -49,14 +49,14 @@ from envstack.node import AESGCMNode, Base64Node, EncryptedNode, FernetNode
49
49
  # value for unresolvable variables
50
50
  null = ""
51
51
 
52
+ # regular expression pattern for matching windows drive letters
53
+ drive_letter_pattern = re.compile(r"(?P<sep>[:;])?(?P<drive>[A-Z]:[/\\])")
54
+
52
55
  # regular expression pattern for bash-like variable expansion
53
56
  variable_pattern = re.compile(
54
- r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([=?])(\$\{[^}]*\}[\-\w/]*|[^}]*))?\}"
57
+ r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([-=?])((?:\$\{[^}]+\}|[^}])*))?\}"
55
58
  )
56
59
 
57
- # regular expression pattern for matching windows drive letters
58
- drive_letter_pattern = re.compile(r"(?P<sep>[:;])?(?P<drive>[A-Z]:[/\\])")
59
-
60
60
 
61
61
  def clear_sys_path(var: str = "PYTHONPATH"):
62
62
  """
@@ -72,9 +72,9 @@ def clear_sys_path(var: str = "PYTHONPATH"):
72
72
  def decode_value(value: str):
73
73
  """Returns a decoded value that's been encoded by a wrapper.
74
74
 
75
- Decoding encoded environments can be tricky. For example, it must account for path
76
- templates that include curly braces, e.g. path templates string like this must be
77
- preserved:
75
+ Decoding encoded environments can be tricky. For example, it must account
76
+ for path templates that include curly braces, e.g. path templates string
77
+ like this must be preserved:
78
78
 
79
79
  '/path/with/{variable}'
80
80
 
@@ -270,6 +270,16 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
270
270
  error message.
271
271
  """
272
272
 
273
+ def sanitize_value(value):
274
+ """Sanitize the value before returning it."""
275
+ # HACK: remove trailing curly braces if they exist
276
+ if type(value) is str and value.endswith("}") and not value.startswith("${"):
277
+ return value.rstrip("}")
278
+ # sanitize path-like values
279
+ elif type(value) is str and ":" in value and ("/" in value or "\\" in value):
280
+ return dedupe_paths(value)
281
+ return value
282
+
273
283
  def substitute_variable(match):
274
284
  """Substitute a variable match with its value."""
275
285
  var_name = match.group(1)
@@ -288,17 +298,20 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
288
298
  else:
289
299
  value = value.replace(varstr, "")
290
300
 
291
- if operator == "=":
301
+ # ${VAR:=default} or ${VAR:-default}
302
+ if operator in ("=", "-"):
292
303
  if override:
293
304
  value = override
294
305
  elif variable_pattern.search(value) or value is None:
295
306
  value = evaluate_modifiers(argument, environ)
296
307
  else:
297
308
  value = value or argument
309
+ # ${VAR:?error message}
298
310
  elif operator == "?":
299
311
  if not value:
300
312
  error_message = argument if argument else f"{var_name} is not set"
301
313
  raise ValueError(error_message)
314
+ # handle recursive references
302
315
  elif variable_pattern.search(value):
303
316
  value = evaluate_modifiers(value, environ)
304
317
  # handle simple ${VAR} substitution
@@ -311,19 +324,10 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
311
324
  # substitute all matches in the expression
312
325
  result = variable_pattern.sub(substitute_variable, expression)
313
326
 
314
- # HACK: remove trailing curly braces if they exist
315
- if result.endswith("}") and not result.startswith("${"):
316
- result = result.rstrip("}")
317
-
318
327
  # evaluate any remaining modifiers, eg. ${VAR:=${FOO:=bar}}
319
328
  if variable_pattern.search(result):
320
329
  result = evaluate_modifiers(result, environ)
321
330
 
322
- # dedupe path-like values and resolve separators
323
- # TODO: replace with regex pattern to detect path-like strings
324
- elif ":" in result and ("/" in result or "\\" in result):
325
- result = dedupe_paths(result)
326
-
327
331
  # detect recursion errors
328
332
  except RecursionError:
329
333
  raise CyclicalReference(f"Cyclical reference detected in {expression}")
@@ -340,27 +344,15 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
340
344
  elif isinstance(expression, FernetNode):
341
345
  result = expression.resolve(env=environ)
342
346
  elif isinstance(expression, list):
343
- result = [
344
- (
345
- variable_pattern.sub(substitute_variable, str(v))
346
- if isinstance(v, str)
347
- else v
348
- )
349
- for v in expression
350
- ]
347
+ result = [(evaluate_modifiers(v, environ)) for v in expression]
351
348
  elif isinstance(expression, dict):
352
349
  result = {
353
- k: (
354
- variable_pattern.sub(substitute_variable, str(v))
355
- if isinstance(v, str)
356
- else v
357
- )
358
- for k, v in expression.items()
350
+ k: (evaluate_modifiers(v, environ)) for k, v in expression.items()
359
351
  }
360
352
  else:
361
353
  result = expression
362
354
 
363
- return result
355
+ return sanitize_value(result)
364
356
 
365
357
 
366
358
  def load_sys_path(
@@ -571,9 +563,9 @@ def partition_platform_data(data: dict):
571
563
  :param data: dictionary to partition.
572
564
  :returns: platform partitioned dictionary.
573
565
  """
574
- # ensure all is present
566
+ # ensure "all" key is present
575
567
  if "all" not in data:
576
- data["all"] = {} # data.copy()
568
+ data["all"] = dict((k, v) for k, v in data.items() if k != "include")
577
569
 
578
570
  # platforms of interest (darwin, linux, windows)
579
571
  platforms = ["darwin", "linux", "windows"]