vortex-nwp 2.0.0__py3-none-any.whl

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 (144) hide show
  1. vortex/__init__.py +159 -0
  2. vortex/algo/__init__.py +13 -0
  3. vortex/algo/components.py +2462 -0
  4. vortex/algo/mpitools.py +1953 -0
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  7. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  8. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  9. vortex/algo/serversynctools.py +171 -0
  10. vortex/config.py +112 -0
  11. vortex/data/__init__.py +19 -0
  12. vortex/data/abstractstores.py +1510 -0
  13. vortex/data/containers.py +835 -0
  14. vortex/data/contents.py +622 -0
  15. vortex/data/executables.py +275 -0
  16. vortex/data/flow.py +119 -0
  17. vortex/data/geometries.ini +2689 -0
  18. vortex/data/geometries.py +799 -0
  19. vortex/data/handlers.py +1230 -0
  20. vortex/data/outflow.py +67 -0
  21. vortex/data/providers.py +487 -0
  22. vortex/data/resources.py +207 -0
  23. vortex/data/stores.py +1390 -0
  24. vortex/data/sync_templates/__init__.py +0 -0
  25. vortex/gloves.py +309 -0
  26. vortex/layout/__init__.py +20 -0
  27. vortex/layout/contexts.py +577 -0
  28. vortex/layout/dataflow.py +1220 -0
  29. vortex/layout/monitor.py +969 -0
  30. vortex/nwp/__init__.py +14 -0
  31. vortex/nwp/algo/__init__.py +21 -0
  32. vortex/nwp/algo/assim.py +537 -0
  33. vortex/nwp/algo/clim.py +1086 -0
  34. vortex/nwp/algo/coupling.py +831 -0
  35. vortex/nwp/algo/eda.py +840 -0
  36. vortex/nwp/algo/eps.py +785 -0
  37. vortex/nwp/algo/forecasts.py +886 -0
  38. vortex/nwp/algo/fpserver.py +1303 -0
  39. vortex/nwp/algo/ifsnaming.py +463 -0
  40. vortex/nwp/algo/ifsroot.py +404 -0
  41. vortex/nwp/algo/monitoring.py +263 -0
  42. vortex/nwp/algo/mpitools.py +694 -0
  43. vortex/nwp/algo/odbtools.py +1258 -0
  44. vortex/nwp/algo/oopsroot.py +916 -0
  45. vortex/nwp/algo/oopstests.py +220 -0
  46. vortex/nwp/algo/request.py +660 -0
  47. vortex/nwp/algo/stdpost.py +1641 -0
  48. vortex/nwp/data/__init__.py +30 -0
  49. vortex/nwp/data/assim.py +380 -0
  50. vortex/nwp/data/boundaries.py +314 -0
  51. vortex/nwp/data/climfiles.py +521 -0
  52. vortex/nwp/data/configfiles.py +153 -0
  53. vortex/nwp/data/consts.py +954 -0
  54. vortex/nwp/data/ctpini.py +149 -0
  55. vortex/nwp/data/diagnostics.py +209 -0
  56. vortex/nwp/data/eda.py +147 -0
  57. vortex/nwp/data/eps.py +432 -0
  58. vortex/nwp/data/executables.py +1045 -0
  59. vortex/nwp/data/fields.py +111 -0
  60. vortex/nwp/data/gridfiles.py +380 -0
  61. vortex/nwp/data/logs.py +584 -0
  62. vortex/nwp/data/modelstates.py +363 -0
  63. vortex/nwp/data/monitoring.py +193 -0
  64. vortex/nwp/data/namelists.py +696 -0
  65. vortex/nwp/data/obs.py +840 -0
  66. vortex/nwp/data/oopsexec.py +74 -0
  67. vortex/nwp/data/providers.py +207 -0
  68. vortex/nwp/data/query.py +206 -0
  69. vortex/nwp/data/stores.py +160 -0
  70. vortex/nwp/data/surfex.py +337 -0
  71. vortex/nwp/syntax/__init__.py +9 -0
  72. vortex/nwp/syntax/stdattrs.py +437 -0
  73. vortex/nwp/tools/__init__.py +10 -0
  74. vortex/nwp/tools/addons.py +40 -0
  75. vortex/nwp/tools/agt.py +67 -0
  76. vortex/nwp/tools/bdap.py +59 -0
  77. vortex/nwp/tools/bdcp.py +41 -0
  78. vortex/nwp/tools/bdm.py +24 -0
  79. vortex/nwp/tools/bdmp.py +54 -0
  80. vortex/nwp/tools/conftools.py +1661 -0
  81. vortex/nwp/tools/drhook.py +66 -0
  82. vortex/nwp/tools/grib.py +294 -0
  83. vortex/nwp/tools/gribdiff.py +104 -0
  84. vortex/nwp/tools/ifstools.py +203 -0
  85. vortex/nwp/tools/igastuff.py +273 -0
  86. vortex/nwp/tools/mars.py +68 -0
  87. vortex/nwp/tools/odb.py +657 -0
  88. vortex/nwp/tools/partitioning.py +258 -0
  89. vortex/nwp/tools/satrad.py +71 -0
  90. vortex/nwp/util/__init__.py +6 -0
  91. vortex/nwp/util/async.py +212 -0
  92. vortex/nwp/util/beacon.py +40 -0
  93. vortex/nwp/util/diffpygram.py +447 -0
  94. vortex/nwp/util/ens.py +279 -0
  95. vortex/nwp/util/hooks.py +139 -0
  96. vortex/nwp/util/taskdeco.py +85 -0
  97. vortex/nwp/util/usepygram.py +697 -0
  98. vortex/nwp/util/usetnt.py +101 -0
  99. vortex/proxy.py +6 -0
  100. vortex/sessions.py +374 -0
  101. vortex/syntax/__init__.py +9 -0
  102. vortex/syntax/stdattrs.py +867 -0
  103. vortex/syntax/stddeco.py +185 -0
  104. vortex/toolbox.py +1117 -0
  105. vortex/tools/__init__.py +20 -0
  106. vortex/tools/actions.py +523 -0
  107. vortex/tools/addons.py +316 -0
  108. vortex/tools/arm.py +96 -0
  109. vortex/tools/compression.py +325 -0
  110. vortex/tools/date.py +27 -0
  111. vortex/tools/ddhpack.py +10 -0
  112. vortex/tools/delayedactions.py +782 -0
  113. vortex/tools/env.py +541 -0
  114. vortex/tools/folder.py +834 -0
  115. vortex/tools/grib.py +738 -0
  116. vortex/tools/lfi.py +953 -0
  117. vortex/tools/listings.py +423 -0
  118. vortex/tools/names.py +637 -0
  119. vortex/tools/net.py +2124 -0
  120. vortex/tools/odb.py +10 -0
  121. vortex/tools/parallelism.py +368 -0
  122. vortex/tools/prestaging.py +210 -0
  123. vortex/tools/rawfiles.py +10 -0
  124. vortex/tools/schedulers.py +480 -0
  125. vortex/tools/services.py +940 -0
  126. vortex/tools/storage.py +996 -0
  127. vortex/tools/surfex.py +61 -0
  128. vortex/tools/systems.py +3976 -0
  129. vortex/tools/targets.py +440 -0
  130. vortex/util/__init__.py +9 -0
  131. vortex/util/config.py +1122 -0
  132. vortex/util/empty.py +24 -0
  133. vortex/util/helpers.py +216 -0
  134. vortex/util/introspection.py +69 -0
  135. vortex/util/iosponge.py +80 -0
  136. vortex/util/roles.py +49 -0
  137. vortex/util/storefunctions.py +129 -0
  138. vortex/util/structs.py +26 -0
  139. vortex/util/worker.py +162 -0
  140. vortex_nwp-2.0.0.dist-info/METADATA +67 -0
  141. vortex_nwp-2.0.0.dist-info/RECORD +144 -0
  142. vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
  143. vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
  144. vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
vortex/tools/env.py ADDED
@@ -0,0 +1,541 @@
1
+ """
2
+ Advanced environment variables management tools.
3
+ """
4
+
5
+ import collections
6
+ import collections.abc
7
+ import json
8
+ import os
9
+ import re
10
+ import traceback
11
+
12
+ from bronx.fancies import loggers
13
+ from bronx.stdtypes.history import PrivateHistory
14
+ from vortex.util.structs import ShellEncoder
15
+
16
+ #: No automatic export
17
+ __all__ = []
18
+
19
+ logger = loggers.getLogger(__name__)
20
+
21
+ #: Pre-compiled evaluation mostly used by :class:`Environment` method (true).
22
+ vartrue = re.compile(
23
+ r"^\s*(?:[1-9]\d*|ok|on|true|yes|y)\s*$", flags=re.IGNORECASE
24
+ )
25
+
26
+ #: Pre-compiled evaluation mostly used by :class:`Environment` method (false).
27
+ varfalse = re.compile(r"^\s*(?:0|ko|off|false|no|n)\s*$", flags=re.IGNORECASE)
28
+
29
+
30
+ def current():
31
+ """Return the current active :class:`Environment` object."""
32
+ return Environment.current()
33
+
34
+
35
+ class Environment:
36
+ """
37
+ Advanced handling of environment features. Either for binding to the system
38
+ or to store and broadcast parameters. Activating an environment results
39
+ in the fact that this new environment is binded to the system environment.
40
+
41
+ New objects could be instantiated from an already existing ``env`` and could be
42
+ active or not according to the ``active`` flag given at initialisation time.
43
+
44
+ An :class:`Environment` could be manipulated as an dictionary for the following
45
+ mechanisms:
46
+
47
+ * key access / contains
48
+ * len / keys / values
49
+ * iteration
50
+ * callable
51
+
52
+ """
53
+
54
+ _current_active = None
55
+
56
+ def __init__(
57
+ self,
58
+ env=None,
59
+ active=False,
60
+ clear=False,
61
+ verbose=False,
62
+ noexport=[],
63
+ contextlock=None,
64
+ history=True,
65
+ ):
66
+ """
67
+ :param Environment env: An existing Environment used to initialise this object.
68
+ :param bool active: Is this new environment activated when created.
69
+ :param bool clear: To create an empty environment. (In
70
+ that case the new environment is by default not active).
71
+ :param verbose clear: Activate the verbose mode (every variable exported
72
+ to the system environment will be signalled on stderr).
73
+ :param list[str] noexport: A list of variable names that will never be
74
+ broadcasted to the system environment variables list.
75
+ :param ~vortex.layout.contexts.Context contextlock: The Context
76
+ this environment is associated to. This implies that the
77
+ environment won't activate unless this associated Context is active.
78
+ :param bool history: Record every changes in an
79
+ :class:`~bronx.stdtypes.history.PrivateHistory` object that will be
80
+ accessible through the :attr:`history` property.
81
+ """
82
+ self.__dict__["_history"] = PrivateHistory() if history else None
83
+ self.__dict__["_verbose"] = verbose
84
+ self.__dict__["_frozen"] = collections.deque()
85
+ self.__dict__["_pool"] = dict()
86
+ self.__dict__["_mods"] = set()
87
+ self.__dict__["_sh"] = None
88
+ self.__dict__["_os"] = list()
89
+ if env is not None and isinstance(env, Environment):
90
+ self._env_clone_internals(env, contextlock)
91
+ if verbose:
92
+ try:
93
+ self.__dict__["_sh"] = env._sh
94
+ except AttributeError:
95
+ pass
96
+ else:
97
+ if clear:
98
+ active = False
99
+ else:
100
+ if self._current_active is not None:
101
+ self._env_clone_internals(
102
+ self._current_active, contextlock
103
+ )
104
+ else:
105
+ self._pool.update(os.environ)
106
+ self.__dict__["_noexport"] = [x.upper() for x in noexport]
107
+ self.active(active)
108
+
109
+ def _env_clone_internals(self, env, contextlock):
110
+ self.__dict__["_os"] = env.osstack()
111
+ self.__dict__["_os"].append(env)
112
+ self._pool.update(env.items())
113
+ if contextlock is not None:
114
+ self.__dict__["_contextlock"] = contextlock
115
+ else:
116
+ self.__dict__["_contextlock"] = env.contextlock
117
+
118
+ @property
119
+ def history(self):
120
+ """
121
+ This environment's :class:`~bronx.stdtypes.history.PrivateHistory`
122
+ object (may be ``None``).
123
+ """
124
+ return self._history
125
+
126
+ def _record(self, var, value):
127
+ if self.history is not None:
128
+ self.history.append(var, value, traceback.format_stack()[:-1])
129
+
130
+ def __str__(self):
131
+ return "{:s} | including {:d} variables>".format(
132
+ repr(self).rstrip(">"), len(self)
133
+ )
134
+
135
+ @classmethod
136
+ def current(cls):
137
+ """Return the current active environment object."""
138
+ return cls._current_active
139
+
140
+ def osstack(self):
141
+ """Return a list of the environment binding stack."""
142
+ return self._os[:]
143
+
144
+ @property
145
+ def contextlock(self):
146
+ """
147
+ The :class:`~vortex.layout.contexts.Context` this environment is bound
148
+ to (this might return None).
149
+ """
150
+ return self._contextlock
151
+
152
+ def dumps(self, value):
153
+ """Dump the specified ``value`` as a string (utility function)."""
154
+ if isinstance(value, str):
155
+ obj = str(value)
156
+ elif hasattr(value, "export_dict"):
157
+ obj = value.export_dict()
158
+ elif hasattr(value, "footprint_export"):
159
+ obj = value.footprint_export()
160
+ elif hasattr(value, "__dict__"):
161
+ obj = vars(value)
162
+ else:
163
+ obj = value
164
+ return str(obj)
165
+
166
+ def setvar(self, varname, value, enforce_uppercase=True):
167
+ """Set uppercase ``varname`` to ``value``.
168
+
169
+ :param bool enforce_uppercase: All variable names are changed to upper case (default).
170
+
171
+ Also used as internal for attribute access or dictionary access.
172
+ """
173
+ upvar = varname.upper() if enforce_uppercase else varname
174
+ upvar = str(upvar)
175
+ self._pool[upvar] = value
176
+ self._mods.add(upvar)
177
+ self._record(upvar, value)
178
+ if self.osbound():
179
+ if isinstance(value, str):
180
+ actualvalue = str(value)
181
+ else:
182
+ actualvalue = json.dumps(value, cls=ShellEncoder)
183
+ os.environ[upvar] = actualvalue
184
+ if self.verbose():
185
+ if self.osbound() and self._sh:
186
+ self._sh.stderr(
187
+ "export", "{:s}={:s}".format(upvar, actualvalue)
188
+ )
189
+ logger.debug('Env export %s="%s"', upvar, actualvalue)
190
+
191
+ def __setitem__(self, varname, value):
192
+ return self.setvar(varname, value)
193
+
194
+ def __setattr__(self, varname, value):
195
+ return self.setvar(varname, value)
196
+
197
+ def getvar(self, varname):
198
+ """Get ``varname`` value (this is not case sensitive).
199
+
200
+ Also used as internal for attribute access or dictionary access.
201
+ """
202
+ varname = str(varname)
203
+ if varname in self._pool:
204
+ return self._pool[varname]
205
+ elif varname.upper() in self._pool:
206
+ return self._pool[varname.upper()]
207
+ else:
208
+ return None
209
+
210
+ def __getitem__(self, varname):
211
+ return self.getvar(varname)
212
+
213
+ def __getattr__(self, varname):
214
+ if varname.startswith("_"):
215
+ raise AttributeError
216
+ else:
217
+ return self.getvar(varname)
218
+
219
+ def delvar(self, varname):
220
+ """
221
+ Delete ``varname`` from current environment (this is not case sensitive).
222
+
223
+ Also used as internal for attribute access or dictionary access.
224
+ """
225
+ seen = 0
226
+ varname = str(varname)
227
+ if varname in self._pool:
228
+ seen = 1
229
+ del self._pool[varname]
230
+ if varname.upper() in self._pool:
231
+ seen = 1
232
+ del self._pool[varname.upper()]
233
+ if seen and self.osbound():
234
+ del os.environ[varname.upper()]
235
+ if self.verbose() and self._sh:
236
+ self._sh.stderr("unset", "{:s}".format(varname.upper()))
237
+ if seen:
238
+ self._record(varname.upper(), "!!deleted!!")
239
+
240
+ def __delitem__(self, varname):
241
+ self.delvar(varname)
242
+
243
+ def __delattr__(self, varname):
244
+ self.delvar(varname)
245
+
246
+ def __len__(self):
247
+ return len(self._pool)
248
+
249
+ def __iter__(self):
250
+ yield from self._pool.keys()
251
+
252
+ def __contains__(self, item):
253
+ item = str(item)
254
+ return item in self._pool or item.upper() in self._pool
255
+
256
+ def has_key(self, item):
257
+ """Returns whether ``item`` is defined or not.
258
+
259
+ Also used as internal for dictionary access.
260
+ """
261
+ return item in self
262
+
263
+ def keys(self):
264
+ """Returns the keys of the internal pool of variables."""
265
+ return self._pool.keys()
266
+
267
+ def __call__(self):
268
+ return self.pool()
269
+
270
+ def values(self):
271
+ """Returns the values of the internal pool of variables."""
272
+ return self._pool.values()
273
+
274
+ def pool(self):
275
+ """Returns the reference of the internal pool of variables."""
276
+ return self._pool
277
+
278
+ def get(self, *args):
279
+ """Proxy to the dictionary ``get`` mechanism on the internal pool of variables."""
280
+ return self._pool.get(str(args[0]).upper(), *args[1:])
281
+
282
+ def items(self):
283
+ """Proxy to the dictionary ``items`` method on the internal pool of variables."""
284
+ return self._pool.items()
285
+
286
+ def __eq__(self, other):
287
+ return (
288
+ isinstance(other, type(self))
289
+ and set(self.keys()) == set(other.keys)
290
+ and all([self[k] == other[k] for k in self.keys])
291
+ )
292
+
293
+ def __ne__(self, other):
294
+ return not self == other
295
+
296
+ def update(self, *args, **kw):
297
+ """Set a collection of variables given as a list of iterable items or key-values pairs."""
298
+ argd = list(args)
299
+ argd.append(kw)
300
+ for dico in argd:
301
+ for var, value in dico.items():
302
+ self.setvar(var, value)
303
+
304
+ def delta(self, **kw):
305
+ """
306
+ Temporarily set a collection of variables that could be reversed using
307
+ the :meth:`rewind` method.
308
+ """
309
+ upditems, newitems = (dict(), collections.deque())
310
+ for var, value in kw.items():
311
+ var = str(var)
312
+ if var in self:
313
+ upditems[var] = self.get(var)
314
+ else:
315
+ newitems.append(var)
316
+ self.setvar(var, value)
317
+ self._frozen.append((upditems, newitems))
318
+
319
+ def rewind(self):
320
+ """Come back on last environment delta changes (see the :meth:`delta` method)."""
321
+ if self._frozen:
322
+ upditems, newitems = self._frozen.pop()
323
+ while newitems:
324
+ self.delvar(newitems.pop())
325
+ for var, value in upditems.items():
326
+ self.setvar(var, value)
327
+ else:
328
+ raise RuntimeError("No more delta to be rewinded...")
329
+
330
+ def delta_context(self, **kw):
331
+ """
332
+ Create a context that will automatically create a delta then rewind it
333
+ when exiting.
334
+ """
335
+ return EnvironmentDeltaContext(self, **kw)
336
+
337
+ def default(self, *args, **kw):
338
+ """Set a collection of non defined variables given as a list of iterable items or key-values pairs."""
339
+ argd = list(args)
340
+ argd.append(kw)
341
+ for dico in argd:
342
+ for var, value in dico.items():
343
+ if var not in self:
344
+ self.setvar(var, value)
345
+
346
+ def merge(self, mergenv):
347
+ """Incorporates key-values from ``mergenv`` into current environment.
348
+
349
+ :param Environment mergeenv: The object to be merged in.
350
+ """
351
+ self.update(mergenv.pool())
352
+
353
+ def clear(self):
354
+ """Flush the current pool of variables."""
355
+ return self._pool.clear()
356
+
357
+ def clone(self):
358
+ """Return a non-active copy of the current env."""
359
+ eclone = self.__class__(env=self, active=False)
360
+ try:
361
+ eclone.verbose(self._verbose, self._sh)
362
+ except AttributeError:
363
+ logger.debug(
364
+ "Could not find verbose attributes while cloning env..."
365
+ )
366
+ return eclone
367
+
368
+ def __enter__(self):
369
+ """Activate the environment when entering a context."""
370
+ self.active(True)
371
+ return self
372
+
373
+ def __exit__(self, exc_type, exc_value, traceback): # @UnusedVariable
374
+ """De-activate the environment on context's exit."""
375
+ self.active(False)
376
+
377
+ def native(self, varname):
378
+ """Returns the native form this variable could have in a shell environment."""
379
+ value = self._pool[varname]
380
+ if isinstance(value, str):
381
+ return str(value)
382
+ else:
383
+ return json.dumps(value, cls=ShellEncoder)
384
+
385
+ def verbose(self, switch=None, sh=None, fromenv=None):
386
+ """Switch on or off the verbose mode. Returns actual value."""
387
+ if switch is not None:
388
+ self.__dict__["_verbose"] = bool(switch)
389
+ if sh is not None:
390
+ self.__dict__["_sh"] = sh
391
+ return self.__dict__["_verbose"]
392
+
393
+ def active(self, *args):
394
+ """
395
+ Bind or unbind current environment to the shell environment according to
396
+ a boolean flag given as first argument.
397
+
398
+ Returns current active status after update.
399
+ """
400
+ previous_act = self.osbound()
401
+ osrewind = None
402
+ active = previous_act
403
+ if args and type(args[0]) is bool:
404
+ active = args[0]
405
+ if previous_act and not active and self._os:
406
+ self._record("!!OS_BINDING!!", "Broken...")
407
+ self.__class__._current_active = self._os[-1]
408
+ osrewind = self.__class__._current_active
409
+ if not previous_act and active:
410
+ if self.contextlock is not None and not self.contextlock.active:
411
+ raise RuntimeError(
412
+ "It's not allowed to switch to an Environment "
413
+ + "that belongs to an inactive context"
414
+ )
415
+ self._record("!!OS_BINDING!!", "Acquiring...")
416
+ self.__class__._current_active = self
417
+ osrewind = self.__class__._current_active
418
+ if osrewind:
419
+ os.environ.clear()
420
+ for k in filter(
421
+ lambda x: x not in osrewind._noexport, osrewind._pool.keys()
422
+ ):
423
+ os.environ[k] = osrewind.native(k)
424
+ return active
425
+
426
+ def naked(self):
427
+ """Return ``True`` when the pool of variables is empty."""
428
+ return not bool(self._pool)
429
+
430
+ def modified(self):
431
+ """Return ``True`` when some variables have been modified."""
432
+ return bool(self._mods)
433
+
434
+ def varupdates(self):
435
+ """Return the list of variables names that have been modified so far."""
436
+ return self._mods
437
+
438
+ def osbound(self):
439
+ """Returns whether this current environment is bound to the os.environ."""
440
+ return self is self.__class__._current_active
441
+
442
+ def tracebacks(self):
443
+ """Dump the stack of manipulations of the current environment."""
444
+ if self.history is not None:
445
+ for u_count, stamp, action in self.history:
446
+ varname, value, stack = action
447
+ print("[", stamp, "]", varname, "=", value, "\n")
448
+ for xs in stack:
449
+ print(xs)
450
+
451
+ def osdump(self):
452
+ """Dump the actual values of the OS environment."""
453
+ for k in sorted(os.environ.keys()):
454
+ print('{:s}="{:s}"'.format(k, os.environ[k]))
455
+
456
+ def mydump(self):
457
+ """Dump the actual values of the current environment."""
458
+ for k in sorted(self._pool.keys()):
459
+ print('{:s}="{:s}"'.format(k, str(self._pool[k])))
460
+
461
+ def mkautolist(self, prefix):
462
+ """Return a list of variable starting with the ``prefix`` string."""
463
+ return [
464
+ var + '="' + self.get(var, "") + '"'
465
+ for var in self.keys()
466
+ if var.startswith(prefix)
467
+ ]
468
+
469
+ def trueshell(self):
470
+ """Extract the actual shell name according to env variable SHELL."""
471
+ return re.sub("^.*/", "", self.getvar("shell"))
472
+
473
+ def true(self, varname):
474
+ """Extended boolean positive test of the variable given as argument."""
475
+ return bool(vartrue.match(str(self.getvar(varname))))
476
+
477
+ def false(self, varname):
478
+ """Extended boolean negative test of the variable given as argument."""
479
+ xvar = self.getvar(varname)
480
+ if xvar is None:
481
+ return True
482
+ else:
483
+ return bool(varfalse.match(str(xvar)))
484
+
485
+ def setgenericpath(self, var, value, pos=None):
486
+ """Insert a new path value to a PATH like variable at a given position.
487
+
488
+ :param str var: The environment's variable to modify (case insensitive)
489
+ :param str value: The value that will be inserted in the path
490
+ :param int pos: Where in the path, to insert ``value`` (by default, at the end)
491
+ """
492
+ mypath = self.getvar(var).split(":") if self.getvar(var) else []
493
+ value = str(value)
494
+ while value in mypath:
495
+ mypath.remove(value)
496
+ if pos is None:
497
+ pos = len(mypath)
498
+ mypath.insert(pos, value)
499
+ self.setvar(var, ":".join(mypath))
500
+
501
+ def rmgenericpath(self, var, value):
502
+ """Remove the specified ``value`` from a PATH like variable."""
503
+ mypath = self.getvar(var).split(":") if self.getvar(var) else []
504
+ while value in mypath:
505
+ mypath.remove(value)
506
+ self.setvar(var, ":".join(mypath))
507
+
508
+ def setbinpath(self, value, pos=None):
509
+ """
510
+ Insert a new path ``value`` to the bin search path (i.e. the PATH
511
+ environment variable) at given position.
512
+ """
513
+ self.setgenericpath("PATH", value, pos)
514
+
515
+ def rmbinpath(self, value):
516
+ """
517
+ Remove the specified ``value`` from the bin path (i.e. the PATH
518
+ environment variable).
519
+ """
520
+ self.rmgenericpath("PATH", value)
521
+
522
+
523
+ collections.abc.Mapping.register(Environment)
524
+
525
+
526
+ class EnvironmentDeltaContext:
527
+ """Context that will apply a delta on the Environment and rewind it on exit."""
528
+
529
+ def __init__(self, env, **kw):
530
+ """
531
+ This object should not be created manually (use the
532
+ :meth:`Environment.delta_context` method instead).
533
+ """
534
+ self._env = env
535
+ self._delta = kw
536
+
537
+ def __enter__(self):
538
+ self._env.delta(**self._delta)
539
+
540
+ def __exit__(self, exc_type, exc_value, traceback): # @UnusedVariable
541
+ self._env.rewind()