envstack 0.7.4__tar.gz → 0.7.5__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 (25) hide show
  1. {envstack-0.7.4/lib/envstack.egg-info → envstack-0.7.5}/PKG-INFO +1 -1
  2. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/__init__.py +1 -1
  3. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/util.py +72 -9
  4. {envstack-0.7.4 → envstack-0.7.5/lib/envstack.egg-info}/PKG-INFO +1 -1
  5. {envstack-0.7.4 → envstack-0.7.5}/setup.py +1 -1
  6. {envstack-0.7.4 → envstack-0.7.5}/tests/test_util.py +86 -0
  7. {envstack-0.7.4 → envstack-0.7.5}/LICENSE +0 -0
  8. {envstack-0.7.4 → envstack-0.7.5}/README.md +0 -0
  9. {envstack-0.7.4 → envstack-0.7.5}/dist.json +0 -0
  10. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/cli.py +0 -0
  11. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/config.py +0 -0
  12. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/env.py +0 -0
  13. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/exceptions.py +0 -0
  14. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/logger.py +0 -0
  15. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/path.py +0 -0
  16. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack/wrapper.py +0 -0
  17. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/SOURCES.txt +0 -0
  18. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/dependency_links.txt +0 -0
  19. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/entry_points.txt +0 -0
  20. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/not-zip-safe +0 -0
  21. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/requires.txt +0 -0
  22. {envstack-0.7.4 → envstack-0.7.5}/lib/envstack.egg-info/top_level.txt +0 -0
  23. {envstack-0.7.4 → envstack-0.7.5}/setup.cfg +0 -0
  24. {envstack-0.7.4 → envstack-0.7.5}/tests/test_cmds.py +0 -0
  25. {envstack-0.7.4 → envstack-0.7.5}/tests/test_env.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.7.4
3
+ Version: 0.7.5
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -34,6 +34,6 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.7.4"
37
+ __version__ = "0.7.5"
38
38
 
39
39
  from envstack.env import clear, init, revert, save
@@ -48,11 +48,14 @@ from envstack.exceptions import CyclicalReference
48
48
  # value for unresolvable variables
49
49
  null = ""
50
50
 
51
- # regular expression pattern for Bash-like variable expansion
51
+ # regular expression pattern for bash-like variable expansion
52
52
  variable_pattern = re.compile(
53
53
  r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([=?])(\$\{[a-zA-Z_][a-zA-Z0-9_]*\}|[^}]*))?\}"
54
54
  )
55
55
 
56
+ # regular expression pattern for matching windows drive letters
57
+ drive_letter_pattern = re.compile(r"(?P<sep>[:;])?(?P<drive>[A-Z]:[/\\])")
58
+
56
59
 
57
60
  def clear_sys_path(var: str = "PYTHONPATH"):
58
61
  """
@@ -97,11 +100,67 @@ def dedupe_list(lst: list):
97
100
  deduplicating paths.
98
101
 
99
102
  :param lst: The list to deduplicate.
100
- :return: The deduplicated list.
103
+ :returns: The deduplicated list.
101
104
  """
102
105
  return list(OrderedDict.fromkeys(lst))
103
106
 
104
107
 
108
+ def split_windows_paths(path_str: str):
109
+ """
110
+ Splits a windows-style path string that may contain a mix of colon and
111
+ semicolon delimiters, while preserving drive letter patterns. Drive letters
112
+ must be uppercase.
113
+
114
+ Example:
115
+ Input: "C:\\Program Files\\Python:D:/path2:E:/path3:/usr/local/bin"
116
+ Output: ['C:\\Program Files\\Python', 'D:/path2', 'E:/path3', '/usr/local/bin']
117
+
118
+ :param path_str: The input path string.
119
+ :returns: The split path list.
120
+ """
121
+ result = []
122
+ tokens = [token.strip() for token in path_str.split(";") if token.strip()]
123
+
124
+ for token in tokens:
125
+ # token is windows-style, insert a marker before drive letters
126
+ if re.match(r"^[A-Z]:[/\\]", token) or "\\" in token:
127
+ modified = drive_letter_pattern.sub(lambda m: "|" + m.group("drive"), token)
128
+ # split on the marker, then on colons that are not in drive-letters
129
+ result += [
130
+ p
131
+ for part in modified.split("|")
132
+ for p in re.split(r"(?<![A-Z]):", part)
133
+ if p
134
+ ]
135
+ else:
136
+ result += [p for p in token.split(":") if p]
137
+
138
+ return result
139
+
140
+
141
+ def dedupe_paths(
142
+ path_str: str, joiner: str = os.pathsep, platform: str = config.PLATFORM
143
+ ):
144
+ """
145
+ Deduplicates paths from a colon-separated string.
146
+
147
+ :param path_str: The input path string.
148
+ :param joiner: The path separator to use.
149
+ :platform: The platform to use.
150
+ :returns: The deduplicated path string.
151
+ """
152
+
153
+ if platform == "windows":
154
+ deduped = dedupe_list(split_windows_paths(path_str))
155
+ else:
156
+ deduped = dedupe_list(path_str.split(":"))
157
+
158
+ # remove empty paths
159
+ # deduped = [p for p in deduped if p]
160
+
161
+ return joiner.join(deduped)
162
+
163
+
105
164
  def dict_diff(dict1: dict, dict2: dict):
106
165
  """
107
166
  Compare two dictionaries and return their differences.
@@ -238,7 +297,7 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
238
297
 
239
298
  # dedupe paths and convert to platform-specific path separators
240
299
  if ":" in result:
241
- result = os.pathsep.join(dedupe_list(result.split(":")))
300
+ result = dedupe_paths(result)
242
301
 
243
302
  # detect recursion errors
244
303
  except RecursionError:
@@ -248,16 +307,20 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
248
307
  except TypeError:
249
308
  if isinstance(expression, list):
250
309
  result = [
251
- variable_pattern.sub(substitute_variable, str(v))
252
- if isinstance(v, str)
253
- else v
310
+ (
311
+ variable_pattern.sub(substitute_variable, str(v))
312
+ if isinstance(v, str)
313
+ else v
314
+ )
254
315
  for v in expression
255
316
  ]
256
317
  elif isinstance(expression, dict):
257
318
  result = {
258
- k: variable_pattern.sub(substitute_variable, str(v))
259
- if isinstance(v, str)
260
- else v
319
+ k: (
320
+ variable_pattern.sub(substitute_variable, str(v))
321
+ if isinstance(v, str)
322
+ else v
323
+ )
261
324
  for k, v in expression.items()
262
325
  }
263
326
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.7.4
3
+ Version: 0.7.5
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -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.7.4",
43
+ version="0.7.5",
44
44
  description="Stacked environment variable management system",
45
45
  long_description=long_description,
46
46
  long_description_content_type="text/markdown",
@@ -136,6 +136,92 @@ class TestUtils(unittest.TestCase):
136
136
  get_stack_name(name)
137
137
 
138
138
 
139
+ class TestDedupePaths(unittest.TestCase):
140
+ def test_dedupe_list(self):
141
+ """Test dedupe_list function."""
142
+ from envstack.util import dedupe_list
143
+
144
+ paths = [
145
+ "/usr/bin",
146
+ "/usr/local/bin",
147
+ "/usr/local/bin",
148
+ "/usr/bin",
149
+ "/usr/local/bin",
150
+ "/some/other/path",
151
+ ]
152
+ result = dedupe_list(paths)
153
+ self.assertEqual(result, ["/usr/bin", "/usr/local/bin", "/some/other/path"])
154
+
155
+ paths = ["/usr/bin"]
156
+ result = dedupe_list(paths)
157
+ self.assertEqual(result, ["/usr/bin"])
158
+
159
+ paths = []
160
+ result = dedupe_list(paths)
161
+ self.assertEqual(result, [])
162
+
163
+ def test_dedupe_paths(self):
164
+ """Test dedupe_paths function."""
165
+ from envstack.util import dedupe_paths
166
+
167
+ paths = [
168
+ "/usr/bin",
169
+ "/usr/local/bin",
170
+ "/usr/bin",
171
+ "/usr/local/bin",
172
+ "/usr/local/bin",
173
+ "/some/other/path",
174
+ ]
175
+ result = dedupe_paths(":".join(paths))
176
+ self.assertEqual(result, "/usr/bin:/usr/local/bin:/some/other/path")
177
+
178
+ paths = ["/usr/bin"]
179
+ result = dedupe_paths(":".join(paths))
180
+ self.assertEqual(result, "/usr/bin")
181
+
182
+ paths = ["/usr/bin", ""]
183
+ result = dedupe_paths(":".join(paths))
184
+ self.assertEqual(result, "/usr/bin:")
185
+
186
+ paths = []
187
+ result = dedupe_paths(":".join(paths))
188
+ self.assertEqual(result, "")
189
+
190
+ def test_dedupe_paths_windows(self):
191
+ """Test dedupe_paths function on windows."""
192
+ from envstack.util import dedupe_paths
193
+
194
+ paths = [
195
+ "C:\\Program Files\\Python",
196
+ "D:/path2",
197
+ "E:/path3",
198
+ ]
199
+ result = dedupe_paths(":".join(paths), joiner=";", platform="windows")
200
+ self.assertEqual(result, "C:\\Program Files\\Python;D:/path2;E:/path3")
201
+
202
+ paths = [
203
+ "C:\\Program Files\\Python",
204
+ "C:\\Program Files\\Python",
205
+ "D:/path2",
206
+ "E:/path3",
207
+ "E:/path3",
208
+ "/usr/local/bin",
209
+ ]
210
+ path = ":".join(paths)
211
+ result = dedupe_paths(path, joiner=";", platform="windows")
212
+ self.assertEqual(result, "C:\\Program Files\\Python;D:/path2;E:/path3;/usr/local/bin")
213
+
214
+ # mixed paths
215
+ path = "X:/pipe/prod/env;X:/pipe/prod/env:/home/user/envstack/env"
216
+ result = dedupe_paths(path, joiner=";", platform="windows")
217
+ self.assertEqual(result, "X:/pipe/prod/env;/home/user/envstack/env")
218
+
219
+ # mixed paths with duplicate
220
+ path = "C:\\Program Files\\Python;D:/path2;E:/path3:/usr/local/bin:/usr/local/bin"
221
+ result = dedupe_paths(path, joiner=";", platform="windows")
222
+ self.assertEqual(result, "C:\\Program Files\\Python;D:/path2;E:/path3;/usr/local/bin")
223
+
224
+
139
225
  class TestSafeEval(unittest.TestCase):
140
226
  def test_safe_eval_string(self):
141
227
  value = "hello"
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