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