vortex-nwp 2.0.0b1__py3-none-any.whl → 2.0.0b2__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.
- vortex/__init__.py +59 -45
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +940 -614
- vortex/algo/mpitools.py +802 -497
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +428 -225
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +544 -413
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +420 -271
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +210 -122
- vortex/nwp/algo/monitoring.py +132 -76
- vortex/nwp/algo/mpitools.py +321 -191
- vortex/nwp/algo/odbtools.py +617 -353
- vortex/nwp/algo/oopsroot.py +449 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +125 -59
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +311 -149
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +632 -298
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +402 -333
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1211 -631
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +377 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.0.0b2.dist-info/METADATA +66 -0
- vortex_nwp-2.0.0b2.dist-info/RECORD +142 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/top_level.txt +0 -0
vortex/util/config.py
CHANGED
|
@@ -9,9 +9,8 @@ It returns an object compliant with the interface defined in
|
|
|
9
9
|
import abc
|
|
10
10
|
from configparser import NoOptionError, NoSectionError, InterpolationDepthError
|
|
11
11
|
from configparser import ConfigParser
|
|
12
|
-
import contextlib
|
|
13
|
-
import importlib
|
|
14
12
|
import itertools
|
|
13
|
+
from pathlib import Path
|
|
15
14
|
import re
|
|
16
15
|
import string
|
|
17
16
|
|
|
@@ -25,7 +24,7 @@ __all__ = []
|
|
|
25
24
|
|
|
26
25
|
logger = loggers.getLogger(__name__)
|
|
27
26
|
|
|
28
|
-
_RE_AUTO_TPL = re.compile(r
|
|
27
|
+
_RE_AUTO_TPL = re.compile(r"^@(([^/].*)\.tpl)$")
|
|
29
28
|
|
|
30
29
|
_RE_ENCODING = re.compile(r"^\s*#.*?coding[=:]\s*([-\w.]+)")
|
|
31
30
|
|
|
@@ -41,24 +40,20 @@ class AbstractTemplatingAdapter(metaclass=abc.ABCMeta):
|
|
|
41
40
|
that should be used during template rendering.
|
|
42
41
|
"""
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def __init__(self, tpl_str, tpl_file, tpl_encoding, tpl_dirs):
|
|
43
|
+
def __init__(self, tpl_str, tpl_file, tpl_encoding):
|
|
47
44
|
"""
|
|
48
45
|
:param tpl_str: The template (as a string)
|
|
49
|
-
:param tpl_file: The template filename (
|
|
46
|
+
:param tpl_file: The template filename (path object)
|
|
50
47
|
:param tpl_encoding: The template encoding (when read from disk)
|
|
51
|
-
:param tpl_dirs: The lookup directories for additional templates
|
|
52
48
|
"""
|
|
53
49
|
self._tpl_file = tpl_file
|
|
54
50
|
self._tpl_encoding = tpl_encoding
|
|
55
|
-
self._tpl_dirs = tpl_dirs
|
|
56
51
|
self._tpl_obj = self._rendering_tool_init(tpl_str)
|
|
57
52
|
|
|
58
53
|
@property
|
|
59
54
|
def srcfile(self):
|
|
60
55
|
"""The template filename (when read from disk)."""
|
|
61
|
-
return self._tpl_file
|
|
56
|
+
return str(self._tpl_file)
|
|
62
57
|
|
|
63
58
|
@abc.abstractmethod
|
|
64
59
|
def _rendering_tool_init(self, tpl_str):
|
|
@@ -70,7 +65,7 @@ class AbstractTemplatingAdapter(metaclass=abc.ABCMeta):
|
|
|
70
65
|
for m in kargs:
|
|
71
66
|
todo.update(m)
|
|
72
67
|
todo.update(kwargs)
|
|
73
|
-
return self(**
|
|
68
|
+
return self(**todo)
|
|
74
69
|
|
|
75
70
|
safe_substitute = substitute
|
|
76
71
|
|
|
@@ -86,8 +81,6 @@ class LegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
86
81
|
See :class:`AbstractTemplatingAdapter` for more details on this class usage.
|
|
87
82
|
"""
|
|
88
83
|
|
|
89
|
-
KIND = 'legacy'
|
|
90
|
-
|
|
91
84
|
def _rendering_tool_init(self, tpl_str):
|
|
92
85
|
return string.Template(tpl_str)
|
|
93
86
|
|
|
@@ -108,8 +101,6 @@ class TwoPassLegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
108
101
|
See :class:`AbstractTemplatingAdapter` for more details on this class usage.
|
|
109
102
|
"""
|
|
110
103
|
|
|
111
|
-
KIND = 'twopasslegacy'
|
|
112
|
-
|
|
113
104
|
def _rendering_tool_init(self, tpl_str):
|
|
114
105
|
return string.Template(tpl_str)
|
|
115
106
|
|
|
@@ -121,86 +112,49 @@ class TwoPassLegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
121
112
|
|
|
122
113
|
def __call__(self, **kwargs):
|
|
123
114
|
"""Render the template using the kwargs dictionary."""
|
|
124
|
-
return string.Template(self._tpl_obj.substitute(kwargs)).substitute(
|
|
115
|
+
return string.Template(self._tpl_obj.substitute(kwargs)).substitute(
|
|
116
|
+
kwargs
|
|
117
|
+
)
|
|
125
118
|
|
|
126
119
|
|
|
127
|
-
|
|
128
|
-
""
|
|
120
|
+
_TEMPLATE_RENDERING_CLASSES = {
|
|
121
|
+
"legacy": LegacyTemplatingAdapter,
|
|
122
|
+
"twopasslegacy": TwoPassLegacyTemplatingAdapter,
|
|
123
|
+
}
|
|
129
124
|
|
|
130
|
-
It requires the, external, :mod:`jinja2` package. Please refer to the
|
|
131
|
-
jinja2 documentation for more details on the jinja2 templating language.
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
def register_template_renderer(key, cls):
|
|
127
|
+
_TEMPLATE_RENDERING_CLASSES[key] = cls
|
|
135
128
|
|
|
136
|
-
KIND = 'jinja2'
|
|
137
|
-
|
|
138
|
-
@contextlib.contextmanager
|
|
139
|
-
def _elaborate_on_jinja2_error(self):
|
|
140
|
-
import jinja2
|
|
141
|
-
try:
|
|
142
|
-
yield
|
|
143
|
-
except jinja2.exceptions.TemplateError as e:
|
|
144
|
-
if isinstance(e, jinja2.exceptions.TemplateSyntaxError):
|
|
145
|
-
logger.error("%s exception while processing Jinja2 templates:\n" +
|
|
146
|
-
" Toplevel template file: %s\n"
|
|
147
|
-
" Jinja2 template info : %s (line %d)\n" +
|
|
148
|
-
" Jinja2 error message : %s",
|
|
149
|
-
e.__class__, self._tpl_file, e.name, e.lineno, e.message)
|
|
150
|
-
else:
|
|
151
|
-
logger.error("%s exception while processing Jinja2 templates:\n" +
|
|
152
|
-
" Toplevel template file: %s",
|
|
153
|
-
e.__class__, self._tpl_file)
|
|
154
|
-
raise
|
|
155
|
-
|
|
156
|
-
def _rendering_tool_init(self, tpl_str):
|
|
157
|
-
import jinja2
|
|
158
|
-
loader = jinja2.FileSystemLoader(
|
|
159
|
-
self._tpl_dirs,
|
|
160
|
-
encoding=self._tpl_encoding,
|
|
161
|
-
followlinks=True) if self._tpl_dirs else None
|
|
162
|
-
j_env = jinja2.Environment(loader=loader, autoescape=False)
|
|
163
|
-
with self._elaborate_on_jinja2_error():
|
|
164
|
-
return j_env.from_string(tpl_str)
|
|
165
129
|
|
|
166
|
-
|
|
167
|
-
"""Render the template using the kwargs dictionary."""
|
|
168
|
-
with self._elaborate_on_jinja2_error():
|
|
169
|
-
return self._tpl_obj.render(kwargs)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def load_template(t, tplfile, encoding=None, version=None, default_templating='legacy'):
|
|
130
|
+
def load_template(tplpath, encoding=None, default_templating="legacy"):
|
|
173
131
|
"""Load a template according to *tplfile*.
|
|
174
132
|
|
|
175
|
-
|
|
176
|
-
:param vortex.sessions.Ticket t: The Vortex' session to be used
|
|
177
|
-
:param str tplfile: The name of the desired template file
|
|
133
|
+
:param str tplpath: path-like object for the template file
|
|
178
134
|
:param str encoding: The characters encoding of the template file
|
|
179
135
|
:param int version: Find a template file with version >= to version
|
|
180
|
-
:param str default_templating: The default templating engine that will
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
136
|
+
:param str default_templating: The default templating engine that will
|
|
137
|
+
be used. The content of the template file is always searched in
|
|
138
|
+
order to detect a "# vortex-templating:" comment that will overrid
|
|
139
|
+
this default.
|
|
184
140
|
:return: A :class:`AbstractTemplatingAdapter` object
|
|
185
141
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
be
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
of the template file. If it exists, the ``kind`` templating engine will be
|
|
203
|
-
used and the ``# vortex-templating:kind`` line will be stripped.
|
|
142
|
+
The characters encoding of the template file may be specified. If
|
|
143
|
+
*encoding* equals ``script``, a line looking like ``#
|
|
144
|
+
encoding:special-encoding`` will be searched for in the first ten
|
|
145
|
+
lines of the template file. If it exists, the ``special-encoding``
|
|
146
|
+
will be used as an encoding and the ``#
|
|
147
|
+
encoding:special-encoding`` line will be stripped from the
|
|
148
|
+
template.
|
|
149
|
+
|
|
150
|
+
Different templating engine may be used to render the template
|
|
151
|
+
file. It defaults to ``legacy`` that is compatible with Python's
|
|
152
|
+
:class:`string.Template` class. However, another default may be
|
|
153
|
+
provided using the *default_templating* argument. In any case, a
|
|
154
|
+
line looking like ``# vortex-templating:kind`` will be searched
|
|
155
|
+
for in the first ten lines of the template file. If it exists, the
|
|
156
|
+
``kind`` templating engine will be used and the ``#
|
|
157
|
+
vortex-templating:kind`` line will be stripped.
|
|
204
158
|
|
|
205
159
|
Currently, only few templating engines are supported:
|
|
206
160
|
|
|
@@ -208,95 +162,51 @@ def load_template(t, tplfile, encoding=None, version=None, default_templating='l
|
|
|
208
162
|
* ``twopasslegacy``: see :class:`TwoPassLegacyTemplatingAdapter`
|
|
209
163
|
* ``jinja2``: see :class:`Jinja2TemplatingAdapter`
|
|
210
164
|
"""
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
autofile = autofile.group(1)
|
|
229
|
-
for dirname in searchdirs:
|
|
230
|
-
filename = t.sh.path.join(dirname, autofile)
|
|
231
|
-
if t.sh.path.exists(filename):
|
|
232
|
-
new_tplfile = filename
|
|
233
|
-
break
|
|
234
|
-
else:
|
|
235
|
-
autofile = autofile.group(2)
|
|
236
|
-
autodir = t.sh.path.dirname(autofile)
|
|
237
|
-
if autodir:
|
|
238
|
-
persodir = t.sh.path.join(persodir, autodir)
|
|
239
|
-
sitedir = t.sh.path.join(sitedir, sitedir)
|
|
240
|
-
autofile = t.sh.path.basename(autofile)
|
|
241
|
-
allowedre = re.compile(autofile + r'-v(\d+).tpl')
|
|
242
|
-
alloweditems = dict()
|
|
243
|
-
for inputdir in (sitedir, persodir):
|
|
244
|
-
if not t.sh.path.exists(inputdir):
|
|
245
|
-
continue
|
|
246
|
-
for fs_item in t.sh.listdir(inputdir):
|
|
247
|
-
fs_match = allowedre.match(fs_item)
|
|
248
|
-
if fs_match:
|
|
249
|
-
alloweditems[int(fs_match.group(1))] = t.sh.path.join(inputdir, fs_item)
|
|
250
|
-
for item_version in sorted(alloweditems.keys(), reverse=True):
|
|
251
|
-
if item_version <= version:
|
|
252
|
-
new_tplfile = alloweditems[item_version]
|
|
253
|
-
break
|
|
254
|
-
if not new_tplfile:
|
|
255
|
-
raise ValueError('Template file not found: <{}> with version >= {!s}.'
|
|
256
|
-
.format(tplfile, version))
|
|
257
|
-
else:
|
|
258
|
-
tplfile = new_tplfile
|
|
259
|
-
try:
|
|
260
|
-
ignored_lines = set()
|
|
261
|
-
actual_encoding = None if encoding == 'script' else encoding
|
|
262
|
-
actual_templating = default_templating
|
|
263
|
-
# To determine the encoding & templating open the file with the default
|
|
264
|
-
# encoding (ignoring decoding errors) and look for comments
|
|
265
|
-
with open(tplfile, errors='replace') as tpfld_tmp:
|
|
266
|
-
if encoding is None:
|
|
267
|
-
actual_encoding = tpfld_tmp.encoding
|
|
268
|
-
# Only inspect the first 10 lines
|
|
269
|
-
for iline, line in enumerate(itertools.islice(tpfld_tmp, 10)):
|
|
270
|
-
# Encoding
|
|
271
|
-
if encoding == 'script':
|
|
272
|
-
encoding_match = _RE_ENCODING.match(line)
|
|
273
|
-
if encoding_match:
|
|
274
|
-
ignored_lines.add(iline)
|
|
275
|
-
actual_encoding = encoding_match.group(1)
|
|
276
|
-
# Templating
|
|
277
|
-
templating_match = _RE_TEMPLATING.match(line)
|
|
278
|
-
if templating_match:
|
|
165
|
+
tplpath = Path(tplpath).absolute()
|
|
166
|
+
if not tplpath.exists():
|
|
167
|
+
raise FileNotFoundError
|
|
168
|
+
ignored_lines = set()
|
|
169
|
+
actual_encoding = None if encoding == "script" else encoding
|
|
170
|
+
actual_templating = default_templating
|
|
171
|
+
# To determine the encoding & templating open the file with the default
|
|
172
|
+
# encoding (ignoring decoding errors) and look for comments
|
|
173
|
+
with open(tplpath, errors="replace") as tpfld_tmp:
|
|
174
|
+
if encoding is None:
|
|
175
|
+
actual_encoding = tpfld_tmp.encoding
|
|
176
|
+
# Only inspect the first 10 lines
|
|
177
|
+
for iline, line in enumerate(itertools.islice(tpfld_tmp, 10)):
|
|
178
|
+
# Encoding
|
|
179
|
+
if encoding == "script":
|
|
180
|
+
encoding_match = _RE_ENCODING.match(line)
|
|
181
|
+
if encoding_match:
|
|
279
182
|
ignored_lines.add(iline)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
183
|
+
actual_encoding = encoding_match.group(1)
|
|
184
|
+
# Templating
|
|
185
|
+
templating_match = _RE_TEMPLATING.match(line)
|
|
186
|
+
if templating_match:
|
|
187
|
+
ignored_lines.add(iline)
|
|
188
|
+
actual_templating = templating_match.group(1)
|
|
189
|
+
# Read the template and delete the encoding line if present
|
|
190
|
+
logger.debug("Opening %s with encoding %s", tplpath, str(actual_encoding))
|
|
191
|
+
with open(tplpath, encoding=actual_encoding) as tpfld:
|
|
192
|
+
tpl_txt = "".join(
|
|
193
|
+
[l for (i, l) in enumerate(tpfld) if i not in ignored_lines]
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
template_rendering_cls = _TEMPLATE_RENDERING_CLASSES[actual_templating]
|
|
198
|
+
except KeyError:
|
|
199
|
+
msg = (
|
|
200
|
+
f"Unknown templating systes < {actual_templating} >"
|
|
201
|
+
f"when trying to load template {tplpath}"
|
|
202
|
+
)
|
|
203
|
+
logger.error(msg)
|
|
204
|
+
|
|
205
|
+
return template_rendering_cls(
|
|
206
|
+
tpl_txt,
|
|
207
|
+
tplpath,
|
|
208
|
+
actual_encoding,
|
|
209
|
+
)
|
|
300
210
|
|
|
301
211
|
|
|
302
212
|
class GenericReadOnlyConfigParser:
|
|
@@ -324,10 +234,17 @@ class GenericReadOnlyConfigParser:
|
|
|
324
234
|
documentation for more details.
|
|
325
235
|
"""
|
|
326
236
|
|
|
327
|
-
_RE_AUTO_SETFILE = re.compile(r
|
|
328
|
-
|
|
329
|
-
def __init__(
|
|
330
|
-
|
|
237
|
+
_RE_AUTO_SETFILE = re.compile(r"^@([^/]+\.ini)$")
|
|
238
|
+
|
|
239
|
+
def __init__(
|
|
240
|
+
self,
|
|
241
|
+
inifile=None,
|
|
242
|
+
parser=None,
|
|
243
|
+
mkforce=False,
|
|
244
|
+
clsparser=_DEFAULT_CONFIG_PARSER,
|
|
245
|
+
encoding=None,
|
|
246
|
+
defaultinifile=None,
|
|
247
|
+
):
|
|
331
248
|
self.parser = parser
|
|
332
249
|
self.mkforce = mkforce
|
|
333
250
|
self.clsparser = clsparser
|
|
@@ -345,7 +262,7 @@ class GenericReadOnlyConfigParser:
|
|
|
345
262
|
|
|
346
263
|
def as_dump(self):
|
|
347
264
|
"""Return a nicely formated class name for dump in footprint."""
|
|
348
|
-
return
|
|
265
|
+
return "file={!s}".format(self.file)
|
|
349
266
|
|
|
350
267
|
def setfile(self, inifile, encoding=None):
|
|
351
268
|
"""Read the specified **inifile** as new configuration.
|
|
@@ -387,12 +304,16 @@ class GenericReadOnlyConfigParser:
|
|
|
387
304
|
glove = sessions.current().glove
|
|
388
305
|
if not isinstance(inifile, str):
|
|
389
306
|
if self.defaultinifile:
|
|
390
|
-
sitedefaultinifile = glove.siteconf +
|
|
307
|
+
sitedefaultinifile = glove.siteconf + "/" + self.defaultinifile
|
|
391
308
|
if local.path.exists(sitedefaultinifile):
|
|
392
309
|
with open(sitedefaultinifile, encoding=encoding) as a_fh:
|
|
393
310
|
self.parser.read_file(a_fh)
|
|
394
311
|
else:
|
|
395
|
-
raise ValueError(
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"Configuration file "
|
|
314
|
+
+ sitedefaultinifile
|
|
315
|
+
+ " not found"
|
|
316
|
+
)
|
|
396
317
|
# Assume it's an IO descriptor
|
|
397
318
|
inifile.seek(0)
|
|
398
319
|
self.parser.read_file(inifile)
|
|
@@ -406,11 +327,13 @@ class GenericReadOnlyConfigParser:
|
|
|
406
327
|
if local.path.exists(inifile):
|
|
407
328
|
filestack.append(local.path.abspath(inifile))
|
|
408
329
|
else:
|
|
409
|
-
raise ValueError(
|
|
330
|
+
raise ValueError(
|
|
331
|
+
"Configuration file " + inifile + " not found"
|
|
332
|
+
)
|
|
410
333
|
else:
|
|
411
334
|
autofile = autofile.group(1)
|
|
412
|
-
sitefile = glove.siteconf +
|
|
413
|
-
persofile = glove.configrc +
|
|
335
|
+
sitefile = glove.siteconf + "/" + autofile
|
|
336
|
+
persofile = glove.configrc + "/" + autofile
|
|
414
337
|
if local.path.exists(sitefile):
|
|
415
338
|
filestack.append(sitefile)
|
|
416
339
|
if local.path.exists(persofile):
|
|
@@ -421,14 +344,20 @@ class GenericReadOnlyConfigParser:
|
|
|
421
344
|
local.filecocoon(persofile)
|
|
422
345
|
local.touch(persofile)
|
|
423
346
|
else:
|
|
424
|
-
raise ValueError(
|
|
347
|
+
raise ValueError(
|
|
348
|
+
"Configuration file " + inifile + " not found"
|
|
349
|
+
)
|
|
425
350
|
if self.defaultinifile:
|
|
426
|
-
sitedefaultinifile = glove.siteconf +
|
|
351
|
+
sitedefaultinifile = glove.siteconf + "/" + self.defaultinifile
|
|
427
352
|
if local.path.exists(sitedefaultinifile):
|
|
428
353
|
# Insert at the beginning (i.e. smallest priority)
|
|
429
354
|
filestack.insert(0, local.path.abspath(sitedefaultinifile))
|
|
430
355
|
else:
|
|
431
|
-
raise ValueError(
|
|
356
|
+
raise ValueError(
|
|
357
|
+
"Configuration file "
|
|
358
|
+
+ sitedefaultinifile
|
|
359
|
+
+ " not found"
|
|
360
|
+
)
|
|
432
361
|
self.file = ",".join(filestack)
|
|
433
362
|
for a_file in filestack:
|
|
434
363
|
with open(a_file, encoding=encoding) as a_fh:
|
|
@@ -444,18 +373,31 @@ class GenericReadOnlyConfigParser:
|
|
|
444
373
|
if merged:
|
|
445
374
|
dico[section] = dict(self.items(section))
|
|
446
375
|
else:
|
|
447
|
-
dico[section] = {
|
|
448
|
-
|
|
376
|
+
dico[section] = {
|
|
377
|
+
k: v
|
|
378
|
+
for k, v in self.items(section)
|
|
379
|
+
if k in self.parser._sections[section]
|
|
380
|
+
}
|
|
449
381
|
return dico
|
|
450
382
|
|
|
451
383
|
def __getattr__(self, attr):
|
|
452
384
|
# Give access to a very limited set of methods
|
|
453
|
-
if attr.startswith(
|
|
454
|
-
|
|
385
|
+
if attr.startswith("get") or attr in (
|
|
386
|
+
"defaults",
|
|
387
|
+
"sections",
|
|
388
|
+
"options",
|
|
389
|
+
"items",
|
|
390
|
+
"has_section",
|
|
391
|
+
"has_option",
|
|
392
|
+
):
|
|
455
393
|
return getattr(self.parser, attr)
|
|
456
394
|
else:
|
|
457
|
-
raise AttributeError(
|
|
458
|
-
|
|
395
|
+
raise AttributeError(
|
|
396
|
+
self.__class__.__name__
|
|
397
|
+
+ " instance has no attribute '"
|
|
398
|
+
+ str(attr)
|
|
399
|
+
+ "'"
|
|
400
|
+
)
|
|
459
401
|
|
|
460
402
|
def footprint_export(self):
|
|
461
403
|
return self.file
|
|
@@ -489,7 +431,7 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
489
431
|
and ``base2``. In case of a conflict, ``base1`` takes precedence over ``base2``.
|
|
490
432
|
"""
|
|
491
433
|
|
|
492
|
-
_RE_VALIDATE = re.compile(r
|
|
434
|
+
_RE_VALIDATE = re.compile(r"([\w-]+)[ \t]*:?")
|
|
493
435
|
_RE_KEYC = re.compile(r"%\(([^)]+)\)s")
|
|
494
436
|
|
|
495
437
|
_max_interpolation_depth = 20
|
|
@@ -503,7 +445,7 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
503
445
|
if self.parser.has_section(zend_section):
|
|
504
446
|
found_sections.append(zend_section)
|
|
505
447
|
for section in self.parser.sections():
|
|
506
|
-
pieces = re.split(r
|
|
448
|
+
pieces = re.split(r"[ \t]*:[ \t]*", section)
|
|
507
449
|
if len(pieces) >= 2 and pieces[0] == zend_section:
|
|
508
450
|
found_sections.append(section)
|
|
509
451
|
for inherited in pieces[1:]:
|
|
@@ -527,12 +469,16 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
527
469
|
else:
|
|
528
470
|
break
|
|
529
471
|
if value and self._RE_KEYC.match(value):
|
|
530
|
-
raise InterpolationDepthError(
|
|
472
|
+
raise InterpolationDepthError(
|
|
473
|
+
self.options(section), section, rawval
|
|
474
|
+
)
|
|
531
475
|
return value
|
|
532
476
|
|
|
533
477
|
def get(self, section, option, raw=False, myvars=None):
|
|
534
478
|
"""Behaves like the GenericConfigParser's ``get`` method."""
|
|
535
|
-
expanded = [
|
|
479
|
+
expanded = [
|
|
480
|
+
s for s in self._get_section_list(section) if s is not None
|
|
481
|
+
]
|
|
536
482
|
if not expanded:
|
|
537
483
|
raise NoSectionError(section)
|
|
538
484
|
expanded.reverse()
|
|
@@ -541,7 +487,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
541
487
|
mydefault = self.defaults().get(option, None)
|
|
542
488
|
for isection in expanded:
|
|
543
489
|
try:
|
|
544
|
-
tmp_result = self.parser.get(
|
|
490
|
+
tmp_result = self.parser.get(
|
|
491
|
+
isection, option, raw=True, vars=myvars
|
|
492
|
+
)
|
|
545
493
|
if tmp_result is not mydefault:
|
|
546
494
|
acc_result = tmp_result
|
|
547
495
|
except NoOptionError as err:
|
|
@@ -558,7 +506,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
558
506
|
def sections(self):
|
|
559
507
|
"""Behaves like the Python ConfigParser's ``section`` method."""
|
|
560
508
|
seen = set()
|
|
561
|
-
for section_m in [
|
|
509
|
+
for section_m in [
|
|
510
|
+
self._RE_VALIDATE.match(s) for s in self.parser.sections()
|
|
511
|
+
]:
|
|
562
512
|
if section_m is not None:
|
|
563
513
|
seen.add(section_m.group(1))
|
|
564
514
|
return list(seen)
|
|
@@ -571,7 +521,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
571
521
|
"""Behaves like the Python ConfigParser's ``options`` method."""
|
|
572
522
|
expanded = self._get_section_list(section)
|
|
573
523
|
if not expanded:
|
|
574
|
-
return self.parser.options(
|
|
524
|
+
return self.parser.options(
|
|
525
|
+
section
|
|
526
|
+
) # A realistic exception will be thrown !
|
|
575
527
|
options = set()
|
|
576
528
|
for isection in [s for s in expanded]:
|
|
577
529
|
options.update(set(self.parser.options(isection)))
|
|
@@ -583,20 +535,29 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
583
535
|
|
|
584
536
|
def items(self, section, raw=False, myvars=None):
|
|
585
537
|
"""Behaves like the Python ConfigParser's ``items`` method."""
|
|
586
|
-
return [
|
|
538
|
+
return [
|
|
539
|
+
(o, self.get(section, o, raw, myvars))
|
|
540
|
+
for o in self.options(section)
|
|
541
|
+
]
|
|
587
542
|
|
|
588
543
|
def __getattr__(self, attr):
|
|
589
544
|
# Give access to a very limited set of methods
|
|
590
|
-
if attr in (
|
|
545
|
+
if attr in ("defaults",):
|
|
591
546
|
return getattr(self.parser, attr)
|
|
592
547
|
else:
|
|
593
|
-
raise AttributeError(
|
|
594
|
-
|
|
548
|
+
raise AttributeError(
|
|
549
|
+
self.__class__.__name__
|
|
550
|
+
+ " instance has no attribute '"
|
|
551
|
+
+ str(attr)
|
|
552
|
+
+ "'"
|
|
553
|
+
)
|
|
595
554
|
|
|
596
555
|
def as_dict(self, merged=True):
|
|
597
556
|
"""Export the configuration file as a dictionary."""
|
|
598
557
|
if not merged:
|
|
599
|
-
raise ValueError(
|
|
558
|
+
raise ValueError(
|
|
559
|
+
"merged=False is not allowed with ExtendedReadOnlyConfigParser."
|
|
560
|
+
)
|
|
600
561
|
return super().as_dict(merged=True)
|
|
601
562
|
|
|
602
563
|
|
|
@@ -622,9 +583,18 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
622
583
|
documentation for more details.
|
|
623
584
|
"""
|
|
624
585
|
|
|
625
|
-
def __init__(
|
|
626
|
-
|
|
627
|
-
|
|
586
|
+
def __init__(
|
|
587
|
+
self,
|
|
588
|
+
inifile=None,
|
|
589
|
+
parser=None,
|
|
590
|
+
mkforce=False,
|
|
591
|
+
clsparser=_DEFAULT_CONFIG_PARSER,
|
|
592
|
+
encoding=None,
|
|
593
|
+
defaultinifile=None,
|
|
594
|
+
):
|
|
595
|
+
super().__init__(
|
|
596
|
+
inifile, parser, mkforce, clsparser, encoding, defaultinifile
|
|
597
|
+
)
|
|
628
598
|
self.updates = list()
|
|
629
599
|
|
|
630
600
|
def setall(self, kw):
|
|
@@ -636,7 +606,7 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
636
606
|
|
|
637
607
|
def save(self):
|
|
638
608
|
"""Write the current state of the configuration in the inital file."""
|
|
639
|
-
with open(self.file.split(",").pop(),
|
|
609
|
+
with open(self.file.split(",").pop(), "wb") as configfile:
|
|
640
610
|
self.write(configfile)
|
|
641
611
|
|
|
642
612
|
@property
|
|
@@ -650,9 +620,13 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
650
620
|
|
|
651
621
|
def __getattr__(self, attr):
|
|
652
622
|
# Give access to all of the parser's methods
|
|
653
|
-
if attr.startswith(
|
|
654
|
-
raise AttributeError(
|
|
655
|
-
|
|
623
|
+
if attr.startswith("__"):
|
|
624
|
+
raise AttributeError(
|
|
625
|
+
self.__class__.__name__
|
|
626
|
+
+ " instance has no attribute '"
|
|
627
|
+
+ str(attr)
|
|
628
|
+
+ "'"
|
|
629
|
+
)
|
|
656
630
|
return getattr(self.parser, attr)
|
|
657
631
|
|
|
658
632
|
|
|
@@ -678,12 +652,14 @@ class DelayedConfigParser(GenericConfigParser):
|
|
|
678
652
|
|
|
679
653
|
def __getattribute__(self, attr):
|
|
680
654
|
try:
|
|
681
|
-
logger.debug(
|
|
682
|
-
if attr in filter(
|
|
683
|
-
|
|
684
|
-
|
|
655
|
+
logger.debug("Getattr %s < %s >", attr, self)
|
|
656
|
+
if attr in filter(
|
|
657
|
+
lambda x: not x.startswith("_"),
|
|
658
|
+
dir(_DEFAULT_CONFIG_PARSER) + ["setall", "save"],
|
|
659
|
+
):
|
|
660
|
+
object.__getattribute__(self, "refresh")()
|
|
685
661
|
except Exception:
|
|
686
|
-
logger.critical(
|
|
662
|
+
logger.critical("Trouble getattr %s < %s >", attr, self)
|
|
687
663
|
return object.__getattribute__(self, attr)
|
|
688
664
|
|
|
689
665
|
|
|
@@ -709,7 +685,7 @@ class JacketConfigParser(GenericConfigParser):
|
|
|
709
685
|
build on the basis of a comma separated list.
|
|
710
686
|
"""
|
|
711
687
|
s = _DEFAULT_CONFIG_PARSER.get(self, section, option)
|
|
712
|
-
tmplist = s.replace(
|
|
688
|
+
tmplist = s.replace(" ", "").split(",")
|
|
713
689
|
if len(tmplist) > 1:
|
|
714
690
|
return tmplist
|
|
715
691
|
else:
|
|
@@ -738,13 +714,20 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
738
714
|
|
|
739
715
|
"""
|
|
740
716
|
|
|
741
|
-
BUILDERS = StringDecoder.BUILDERS + [
|
|
742
|
-
|
|
743
|
-
|
|
717
|
+
BUILDERS = StringDecoder.BUILDERS + [
|
|
718
|
+
"geometry",
|
|
719
|
+
"date",
|
|
720
|
+
"time",
|
|
721
|
+
"rangex",
|
|
722
|
+
"daterangex",
|
|
723
|
+
"iniconf",
|
|
724
|
+
"conftool",
|
|
725
|
+
]
|
|
744
726
|
|
|
745
727
|
def remap_geometry(self, value):
|
|
746
728
|
"""Convert all values to Geometry objects."""
|
|
747
729
|
from vortex.data import geometries
|
|
730
|
+
|
|
748
731
|
try:
|
|
749
732
|
value = geometries.get(tag=value)
|
|
750
733
|
except ValueError:
|
|
@@ -770,6 +753,7 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
770
753
|
def _build_geometry(self, value, remap, subs):
|
|
771
754
|
val = self._value_expand(value, remap, subs)
|
|
772
755
|
from vortex.data import geometries
|
|
756
|
+
|
|
773
757
|
return geometries.get(tag=val)
|
|
774
758
|
|
|
775
759
|
def _build_date(self, value, remap, subs):
|
|
@@ -784,72 +768,94 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
784
768
|
"""Build a rangex or daterangex from the **value** string."""
|
|
785
769
|
# Try to read names arguments
|
|
786
770
|
try:
|
|
787
|
-
values = self._sparser(value, itemsep=
|
|
788
|
-
if all(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
771
|
+
values = self._sparser(value, itemsep=" ", keysep=":")
|
|
772
|
+
if all(
|
|
773
|
+
[
|
|
774
|
+
k in ("start", "end", "step", "shift", "fmt", "prefix")
|
|
775
|
+
for k in values.keys()
|
|
776
|
+
]
|
|
777
|
+
):
|
|
778
|
+
return cb(
|
|
779
|
+
**{
|
|
780
|
+
k: self._value_expand(v, remap, subs)
|
|
781
|
+
for k, v in values.items()
|
|
782
|
+
}
|
|
783
|
+
)
|
|
792
784
|
except StringDecoderSyntaxError:
|
|
793
785
|
pass
|
|
794
786
|
# The usual case...
|
|
795
|
-
return cb(
|
|
796
|
-
|
|
787
|
+
return cb(
|
|
788
|
+
[
|
|
789
|
+
self._value_expand(v, remap, subs)
|
|
790
|
+
for v in self._sparser(value, itemsep=",")
|
|
791
|
+
]
|
|
792
|
+
)
|
|
797
793
|
|
|
798
794
|
def _build_rangex(self, value, remap, subs):
|
|
799
795
|
"""Build a rangex from the **value** string."""
|
|
800
|
-
return self._build_generic_rangex(
|
|
796
|
+
return self._build_generic_rangex(
|
|
797
|
+
bdate.timeintrangex, value, remap, subs
|
|
798
|
+
)
|
|
801
799
|
|
|
802
800
|
def _build_daterangex(self, value, remap, subs):
|
|
803
801
|
"""Build a daterangex from the **value** string."""
|
|
804
802
|
return self._build_generic_rangex(bdate.daterangex, value, remap, subs)
|
|
805
803
|
|
|
806
804
|
def _build_fpgeneric(self, value, remap, subs, collector):
|
|
807
|
-
fp = {
|
|
808
|
-
|
|
805
|
+
fp = {
|
|
806
|
+
k: self._value_expand(v, remap, subs)
|
|
807
|
+
for k, v in self._sparser(value, itemsep=" ", keysep=":").items()
|
|
808
|
+
}
|
|
809
809
|
obj = footprints.collectors.get(tag=collector).load(**fp)
|
|
810
810
|
if obj is None:
|
|
811
|
-
raise StringDecoderSyntaxError(
|
|
812
|
-
|
|
813
|
-
|
|
811
|
+
raise StringDecoderSyntaxError(
|
|
812
|
+
value,
|
|
813
|
+
"No object could be created from the {} collector".format(
|
|
814
|
+
collector
|
|
815
|
+
),
|
|
816
|
+
)
|
|
814
817
|
return obj
|
|
815
818
|
|
|
816
819
|
def _build_iniconf(self, value, remap, subs):
|
|
817
|
-
return self._build_fpgeneric(value, remap, subs,
|
|
820
|
+
return self._build_fpgeneric(value, remap, subs, "iniconf")
|
|
818
821
|
|
|
819
822
|
def _build_conftool(self, value, remap, subs):
|
|
820
|
-
return self._build_fpgeneric(value, remap, subs,
|
|
823
|
+
return self._build_fpgeneric(value, remap, subs, "conftool")
|
|
821
824
|
|
|
822
825
|
|
|
823
826
|
class IniConf(footprints.FootprintBase):
|
|
824
827
|
"""
|
|
825
828
|
Generic Python configuration file.
|
|
826
829
|
"""
|
|
827
|
-
|
|
830
|
+
|
|
831
|
+
_collector = ("iniconf",)
|
|
828
832
|
_abstract = True
|
|
829
833
|
_footprint = dict(
|
|
830
|
-
info=
|
|
834
|
+
info="Abstract Python Inifile",
|
|
831
835
|
attr=dict(
|
|
832
|
-
kind
|
|
833
|
-
info
|
|
834
|
-
values
|
|
836
|
+
kind=dict(
|
|
837
|
+
info="The configuration object kind.",
|
|
838
|
+
values=[
|
|
839
|
+
"generic",
|
|
840
|
+
],
|
|
835
841
|
),
|
|
836
|
-
clsconfig
|
|
837
|
-
type
|
|
838
|
-
isclass
|
|
839
|
-
optional
|
|
840
|
-
default
|
|
841
|
-
doc_visibility
|
|
842
|
+
clsconfig=dict(
|
|
843
|
+
type=GenericReadOnlyConfigParser,
|
|
844
|
+
isclass=True,
|
|
845
|
+
optional=True,
|
|
846
|
+
default=GenericReadOnlyConfigParser,
|
|
847
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
842
848
|
),
|
|
843
|
-
inifile
|
|
844
|
-
kind
|
|
845
|
-
optional
|
|
846
|
-
default
|
|
849
|
+
inifile=dict(
|
|
850
|
+
kind="The configuration file to look for.",
|
|
851
|
+
optional=True,
|
|
852
|
+
default="@[kind].ini",
|
|
847
853
|
),
|
|
848
|
-
)
|
|
854
|
+
),
|
|
849
855
|
)
|
|
850
856
|
|
|
851
857
|
def __init__(self, *args, **kw):
|
|
852
|
-
logger.debug(
|
|
858
|
+
logger.debug("Ini Conf %s", self.__class__)
|
|
853
859
|
super().__init__(*args, **kw)
|
|
854
860
|
self._config = self.clsconfig(inifile=self.inifile)
|
|
855
861
|
|
|
@@ -864,67 +870,76 @@ class ConfigurationTable(IniConf):
|
|
|
864
870
|
items (instantiated from the tableitem footprint's collector) from a given
|
|
865
871
|
configuration file.
|
|
866
872
|
"""
|
|
873
|
+
|
|
867
874
|
_abstract = True
|
|
868
875
|
_footprint = dict(
|
|
869
|
-
info
|
|
870
|
-
attr
|
|
871
|
-
kind
|
|
872
|
-
info
|
|
876
|
+
info="Abstract configuration tables",
|
|
877
|
+
attr=dict(
|
|
878
|
+
kind=dict(
|
|
879
|
+
info="The configuration's table kind.",
|
|
873
880
|
),
|
|
874
|
-
family
|
|
875
|
-
info
|
|
881
|
+
family=dict(
|
|
882
|
+
info="The configuration's table family.",
|
|
876
883
|
),
|
|
877
|
-
version
|
|
878
|
-
info
|
|
879
|
-
optional
|
|
880
|
-
default
|
|
884
|
+
version=dict(
|
|
885
|
+
info="The configuration's table version.",
|
|
886
|
+
optional=True,
|
|
887
|
+
default="std",
|
|
881
888
|
),
|
|
882
|
-
searchkeys
|
|
883
|
-
info
|
|
884
|
-
type
|
|
885
|
-
optional
|
|
886
|
-
default
|
|
889
|
+
searchkeys=dict(
|
|
890
|
+
info="Item's attributes used to perform the lookup in the find method.",
|
|
891
|
+
type=footprints.FPTuple,
|
|
892
|
+
optional=True,
|
|
893
|
+
default=footprints.FPTuple(),
|
|
887
894
|
),
|
|
888
|
-
groupname
|
|
889
|
-
info
|
|
890
|
-
optional
|
|
891
|
-
default
|
|
895
|
+
groupname=dict(
|
|
896
|
+
info="The class attribute matching the configuration file groupname",
|
|
897
|
+
optional=True,
|
|
898
|
+
default="family",
|
|
892
899
|
),
|
|
893
|
-
inifile
|
|
894
|
-
optional
|
|
895
|
-
default
|
|
900
|
+
inifile=dict(
|
|
901
|
+
optional=True,
|
|
902
|
+
default="@[family]-[kind]-[version].ini",
|
|
896
903
|
),
|
|
897
|
-
clsconfig
|
|
898
|
-
default
|
|
904
|
+
clsconfig=dict(
|
|
905
|
+
default=ExtendedReadOnlyConfigParser,
|
|
899
906
|
),
|
|
900
|
-
language
|
|
901
|
-
info
|
|
902
|
-
optional
|
|
903
|
-
default
|
|
907
|
+
language=dict(
|
|
908
|
+
info="The default language for the translator property.",
|
|
909
|
+
optional=True,
|
|
910
|
+
default="en",
|
|
904
911
|
),
|
|
905
|
-
)
|
|
912
|
+
),
|
|
906
913
|
)
|
|
907
914
|
|
|
908
915
|
@property
|
|
909
916
|
def realkind(self):
|
|
910
|
-
return
|
|
917
|
+
return "configuration-table"
|
|
911
918
|
|
|
912
919
|
def groups(self):
|
|
913
920
|
"""Actual list of items groups described in the current iniconf."""
|
|
914
|
-
return [
|
|
915
|
-
|
|
921
|
+
return [
|
|
922
|
+
x
|
|
923
|
+
for x in self.config.parser.sections()
|
|
924
|
+
if ":" not in x and not x.startswith("lang_")
|
|
925
|
+
]
|
|
916
926
|
|
|
917
927
|
def keys(self):
|
|
918
928
|
"""Actual list of different items in the current iniconf."""
|
|
919
|
-
return [
|
|
920
|
-
|
|
929
|
+
return [
|
|
930
|
+
x
|
|
931
|
+
for x in self.config.sections()
|
|
932
|
+
if x not in self.groups() and not x.startswith("lang_")
|
|
933
|
+
]
|
|
921
934
|
|
|
922
935
|
@property
|
|
923
936
|
def translator(self):
|
|
924
937
|
"""The special section of the iniconf dedicated to translation, as a dict."""
|
|
925
|
-
if not hasattr(self,
|
|
926
|
-
if self.config.has_section(
|
|
927
|
-
self._translator = self.config.as_dict()[
|
|
938
|
+
if not hasattr(self, "_translator"):
|
|
939
|
+
if self.config.has_section("lang_" + self.language):
|
|
940
|
+
self._translator = self.config.as_dict()[
|
|
941
|
+
"lang_" + self.language
|
|
942
|
+
]
|
|
928
943
|
else:
|
|
929
944
|
self._translator = None
|
|
930
945
|
return self._translator
|
|
@@ -932,34 +947,42 @@ class ConfigurationTable(IniConf):
|
|
|
932
947
|
@property
|
|
933
948
|
def tablelist(self):
|
|
934
949
|
"""List of unique instances of items described in the current iniconf."""
|
|
935
|
-
if not hasattr(self,
|
|
950
|
+
if not hasattr(self, "_tablelist"):
|
|
936
951
|
self._tablelist = list()
|
|
937
952
|
d = self.config.as_dict()
|
|
938
|
-
for item, group in [
|
|
953
|
+
for item, group in [
|
|
954
|
+
x.split(":") for x in self.config.parser.sections() if ":" in x
|
|
955
|
+
]:
|
|
939
956
|
try:
|
|
940
957
|
for k, v in d[item].items():
|
|
941
958
|
# Can occur in case of a redundant entry in the config file
|
|
942
959
|
if isinstance(v, str) and v:
|
|
943
|
-
if re.match(
|
|
960
|
+
if re.match("none$", v, re.IGNORECASE):
|
|
944
961
|
d[item][k] = None
|
|
945
|
-
if re.search(
|
|
946
|
-
d[item][k] = v.replace(
|
|
962
|
+
if re.search("[a-z]_[a-z]", v, re.IGNORECASE):
|
|
963
|
+
d[item][k] = v.replace("_", "'")
|
|
947
964
|
d[item][self.searchkeys[0]] = item
|
|
948
965
|
d[item][self.groupname] = group
|
|
949
|
-
d[item][
|
|
966
|
+
d[item]["translator"] = self.translator
|
|
950
967
|
itemobj = footprints.proxy.tableitem(**d[item])
|
|
951
968
|
if itemobj is not None:
|
|
952
969
|
self._tablelist.append(itemobj)
|
|
953
970
|
else:
|
|
954
|
-
logger.error(
|
|
971
|
+
logger.error(
|
|
972
|
+
"Unable to create the %s item object. Check the footprint !",
|
|
973
|
+
item,
|
|
974
|
+
)
|
|
955
975
|
except (KeyError, IndexError):
|
|
956
|
-
logger.warning(
|
|
976
|
+
logger.warning("Some item description could not match")
|
|
957
977
|
return self._tablelist
|
|
958
978
|
|
|
959
979
|
def get(self, item):
|
|
960
980
|
"""Return the item with main key exactly matching the given argument."""
|
|
961
|
-
candidates = [
|
|
962
|
-
|
|
981
|
+
candidates = [
|
|
982
|
+
x
|
|
983
|
+
for x in self.tablelist
|
|
984
|
+
if x.footprint_getattr(self.searchkeys[0]) == item
|
|
985
|
+
]
|
|
963
986
|
if candidates:
|
|
964
987
|
return candidates[0]
|
|
965
988
|
else:
|
|
@@ -967,8 +990,13 @@ class ConfigurationTable(IniConf):
|
|
|
967
990
|
|
|
968
991
|
def match(self, item):
|
|
969
992
|
"""Return the item with main key matching the given argument without case consideration."""
|
|
970
|
-
candidates = [
|
|
971
|
-
|
|
993
|
+
candidates = [
|
|
994
|
+
x
|
|
995
|
+
for x in self.tablelist
|
|
996
|
+
if x.footprint_getattr(self.searchkeys[0])
|
|
997
|
+
.lower()
|
|
998
|
+
.startswith(item.lower())
|
|
999
|
+
]
|
|
972
1000
|
if candidates:
|
|
973
1001
|
return candidates[0]
|
|
974
1002
|
else:
|
|
@@ -976,14 +1004,28 @@ class ConfigurationTable(IniConf):
|
|
|
976
1004
|
|
|
977
1005
|
def grep(self, item):
|
|
978
1006
|
"""Return a list of items with main key loosely matching the given argument."""
|
|
979
|
-
return [
|
|
980
|
-
|
|
1007
|
+
return [
|
|
1008
|
+
x
|
|
1009
|
+
for x in self.tablelist
|
|
1010
|
+
if re.search(
|
|
1011
|
+
item, x.footprint_getattr(self.searchkeys[0]), re.IGNORECASE
|
|
1012
|
+
)
|
|
1013
|
+
]
|
|
981
1014
|
|
|
982
1015
|
def find(self, item):
|
|
983
1016
|
"""Return a list of items with main key or name loosely matching the given argument."""
|
|
984
|
-
return [
|
|
985
|
-
|
|
986
|
-
|
|
1017
|
+
return [
|
|
1018
|
+
x
|
|
1019
|
+
for x in self.tablelist
|
|
1020
|
+
if any(
|
|
1021
|
+
[
|
|
1022
|
+
re.search(
|
|
1023
|
+
item, x.footprint_getattr(thiskey), re.IGNORECASE
|
|
1024
|
+
)
|
|
1025
|
+
for thiskey in self.searchkeys
|
|
1026
|
+
]
|
|
1027
|
+
)
|
|
1028
|
+
]
|
|
987
1029
|
|
|
988
1030
|
|
|
989
1031
|
class TableItem(footprints.FootprintBase):
|
|
@@ -992,27 +1034,27 @@ class TableItem(footprints.FootprintBase):
|
|
|
992
1034
|
"""
|
|
993
1035
|
|
|
994
1036
|
#: Attribute describing the item's name during RST exports
|
|
995
|
-
_RST_NAME =
|
|
1037
|
+
_RST_NAME = ""
|
|
996
1038
|
#: Attributes that will appear on the top line of RST exports
|
|
997
1039
|
_RST_HOTKEYS = []
|
|
998
1040
|
|
|
999
1041
|
_abstract = True
|
|
1000
|
-
_collector = (
|
|
1042
|
+
_collector = ("tableitem",)
|
|
1001
1043
|
_footprint = dict(
|
|
1002
|
-
info
|
|
1003
|
-
attr
|
|
1044
|
+
info="Abstract configuration table's item.",
|
|
1045
|
+
attr=dict(
|
|
1004
1046
|
# Define your own...
|
|
1005
|
-
translator
|
|
1006
|
-
optional
|
|
1007
|
-
type
|
|
1008
|
-
default
|
|
1047
|
+
translator=dict(
|
|
1048
|
+
optional=True,
|
|
1049
|
+
type=footprints.FPDict,
|
|
1050
|
+
default=None,
|
|
1009
1051
|
),
|
|
1010
|
-
)
|
|
1052
|
+
),
|
|
1011
1053
|
)
|
|
1012
1054
|
|
|
1013
1055
|
@property
|
|
1014
1056
|
def realkind(self):
|
|
1015
|
-
return
|
|
1057
|
+
return "tableitem"
|
|
1016
1058
|
|
|
1017
1059
|
def _translated_items(self, mkshort=True):
|
|
1018
1060
|
"""Returns a list of 3-elements tuples describing the item attributes.
|
|
@@ -1021,13 +1063,22 @@ class TableItem(footprints.FootprintBase):
|
|
|
1021
1063
|
"""
|
|
1022
1064
|
output_stack = list()
|
|
1023
1065
|
if self.translator:
|
|
1024
|
-
for k in self.translator.get(
|
|
1066
|
+
for k in self.translator.get("ordered_dump", "").split(","):
|
|
1025
1067
|
if not mkshort or self.footprint_getattr(k) is not None:
|
|
1026
|
-
output_stack.append(
|
|
1027
|
-
|
|
1068
|
+
output_stack.append(
|
|
1069
|
+
(
|
|
1070
|
+
self.translator.get(
|
|
1071
|
+
k, k.replace("_", " ").title()
|
|
1072
|
+
),
|
|
1073
|
+
str(self.footprint_getattr(k)),
|
|
1074
|
+
k,
|
|
1075
|
+
)
|
|
1076
|
+
)
|
|
1028
1077
|
else:
|
|
1029
1078
|
for k in self.footprint_attributes:
|
|
1030
|
-
if (
|
|
1079
|
+
if (
|
|
1080
|
+
not mkshort or self.footprint_getattr(k) is not None
|
|
1081
|
+
) and k != "translator":
|
|
1031
1082
|
output_stack.append((k, str(self.footprint_getattr(k)), k))
|
|
1032
1083
|
return output_stack
|
|
1033
1084
|
|
|
@@ -1037,10 +1088,10 @@ class TableItem(footprints.FootprintBase):
|
|
|
1037
1088
|
output_list = []
|
|
1038
1089
|
if output_stack:
|
|
1039
1090
|
max_keylen = max([len(i[0]) for i in output_stack])
|
|
1040
|
-
print_fmt =
|
|
1091
|
+
print_fmt = "{0:" + str(max_keylen) + "s} : {1:s}"
|
|
1041
1092
|
for item in output_stack:
|
|
1042
1093
|
output_list.append(print_fmt.format(*item))
|
|
1043
|
-
return
|
|
1094
|
+
return "\n".join(output_list)
|
|
1044
1095
|
|
|
1045
1096
|
def __str__(self):
|
|
1046
1097
|
return self.nice_str()
|
|
@@ -1053,7 +1104,7 @@ class TableItem(footprints.FootprintBase):
|
|
|
1053
1104
|
"""Produces a nice ordered RST output of the item attributes."""
|
|
1054
1105
|
assert self._RST_NAME, "Please override _RST_NAME"
|
|
1055
1106
|
output_stack = self._translated_items(mkshort=mkshort)
|
|
1056
|
-
i_name =
|
|
1107
|
+
i_name = "????"
|
|
1057
1108
|
i_hot = []
|
|
1058
1109
|
i_other = []
|
|
1059
1110
|
for item in output_stack:
|
|
@@ -1063,9 +1114,8 @@ class TableItem(footprints.FootprintBase):
|
|
|
1063
1114
|
i_hot.append(item)
|
|
1064
1115
|
else:
|
|
1065
1116
|
i_other.append(item)
|
|
1066
|
-
return
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
)
|
|
1117
|
+
return "**{}** : `{}`\n\n{}\n\n".format(
|
|
1118
|
+
i_name[1],
|
|
1119
|
+
", ".join(["{:s}={:s}".format(*i) for i in i_hot]),
|
|
1120
|
+
"\n".join([" * {:s}: {:s}".format(*i) for i in i_other]),
|
|
1121
|
+
)
|