foamlib 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {foamlib-0.2.2 → foamlib-0.2.4}/PKG-INFO +2 -1
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib/__init__.py +3 -2
- foamlib-0.2.4/foamlib/_dictionaries/__init__.py +8 -0
- foamlib-0.2.4/foamlib/_dictionaries/_base.py +43 -0
- foamlib-0.2.4/foamlib/_dictionaries/_files.py +410 -0
- foamlib-0.2.4/foamlib/_dictionaries/_parsing.py +177 -0
- foamlib-0.2.4/foamlib/_dictionaries/_serialization.py +123 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib.egg-info/PKG-INFO +2 -1
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib.egg-info/SOURCES.txt +5 -1
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib.egg-info/requires.txt +1 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/pyproject.toml +1 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_dictionaries.py +27 -33
- foamlib-0.2.2/foamlib/_dictionaries.py +0 -491
- {foamlib-0.2.2 → foamlib-0.2.4}/LICENSE.txt +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/README.md +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib/_cases.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib/_subprocesses.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib/py.typed +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib.egg-info/dependency_links.txt +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/foamlib.egg-info/top_level.txt +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/setup.cfg +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_basic.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_flange.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_flange_async.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_pitz.py +0 -0
- {foamlib-0.2.2 → foamlib-0.2.4}/tests/test_pitz_async.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.4
|
4
4
|
Summary: A Python interface for interacting with OpenFOAM
|
5
5
|
Author-email: "Gabriel S. Gerlero" <ggerlero@cimec.unl.edu.ar>
|
6
6
|
Project-URL: Homepage, https://github.com/gerlero/foamlib
|
@@ -27,6 +27,7 @@ Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE.txt
|
28
28
|
Requires-Dist: aioshutil<2,>=1
|
29
29
|
Requires-Dist: pyparsing<4,>=3
|
30
|
+
Requires-Dist: typing-extensions<5,>=4
|
30
31
|
Provides-Extra: lint
|
31
32
|
Requires-Dist: mypy<2,>=1; extra == "lint"
|
32
33
|
Requires-Dist: pytest<9,>=7; extra == "lint"
|
@@ -1,7 +1,7 @@
|
|
1
|
-
__version__ = "0.2.
|
1
|
+
__version__ = "0.2.4"
|
2
2
|
|
3
3
|
from ._cases import FoamCase, AsyncFoamCase, FoamCaseBase
|
4
|
-
from ._dictionaries import FoamFile, FoamFieldFile
|
4
|
+
from ._dictionaries import FoamFile, FoamFieldFile, FoamDictionaryBase
|
5
5
|
|
6
6
|
__all__ = [
|
7
7
|
"FoamCase",
|
@@ -9,4 +9,5 @@ __all__ = [
|
|
9
9
|
"FoamCaseBase",
|
10
10
|
"FoamFile",
|
11
11
|
"FoamFieldFile",
|
12
|
+
"FoamDictionaryBase",
|
12
13
|
]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Dict, NamedTuple, Optional, Sequence, Union
|
4
|
+
|
5
|
+
|
6
|
+
class FoamDictionaryBase:
|
7
|
+
class DimensionSet(NamedTuple):
|
8
|
+
mass: Union[int, float] = 0
|
9
|
+
length: Union[int, float] = 0
|
10
|
+
time: Union[int, float] = 0
|
11
|
+
temperature: Union[int, float] = 0
|
12
|
+
moles: Union[int, float] = 0
|
13
|
+
current: Union[int, float] = 0
|
14
|
+
luminous_intensity: Union[int, float] = 0
|
15
|
+
|
16
|
+
def __repr__(self) -> str:
|
17
|
+
return f"{type(self).__qualname__}({', '.join(f'{n}={v}' for n, v in zip(self._fields, self) if v != 0)})"
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class Dimensioned:
|
21
|
+
value: Union[int, float, Sequence[Union[int, float]]] = 0
|
22
|
+
dimensions: Union[
|
23
|
+
"FoamDictionaryBase.DimensionSet", Sequence[Union[int, float]]
|
24
|
+
] = ()
|
25
|
+
name: Optional[str] = None
|
26
|
+
|
27
|
+
def __post_init__(self) -> None:
|
28
|
+
if not isinstance(self.dimensions, FoamDictionaryBase.DimensionSet):
|
29
|
+
self.dimensions = FoamDictionaryBase.DimensionSet(*self.dimensions)
|
30
|
+
|
31
|
+
Value = Union[str, int, float, bool, Dimensioned, DimensionSet, Sequence["Value"]]
|
32
|
+
"""
|
33
|
+
A value that can be stored in an OpenFOAM dictionary.
|
34
|
+
"""
|
35
|
+
|
36
|
+
_Dict = Dict[str, Union["FoamDictionaryBase.Value", "_Dict"]]
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
def as_dict(self) -> _Dict:
|
40
|
+
"""
|
41
|
+
Return a nested dict representation of the dictionary.
|
42
|
+
"""
|
43
|
+
raise NotImplementedError
|
@@ -0,0 +1,410 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Iterator,
|
5
|
+
Mapping,
|
6
|
+
MutableMapping,
|
7
|
+
Optional,
|
8
|
+
Sequence,
|
9
|
+
Tuple,
|
10
|
+
Union,
|
11
|
+
cast,
|
12
|
+
)
|
13
|
+
|
14
|
+
from typing_extensions import Self
|
15
|
+
|
16
|
+
from ._base import FoamDictionaryBase
|
17
|
+
from ._parsing import Parsed, as_dict, get_entry_locn, get_value, parse
|
18
|
+
from ._serialization import serialize_entry
|
19
|
+
|
20
|
+
try:
|
21
|
+
import numpy as np
|
22
|
+
from numpy.typing import NDArray
|
23
|
+
except ModuleNotFoundError:
|
24
|
+
pass
|
25
|
+
|
26
|
+
|
27
|
+
class _FoamFileBase:
|
28
|
+
def __init__(self, path: Union[str, Path]) -> None:
|
29
|
+
self.path = Path(path).absolute()
|
30
|
+
if self.path.is_dir():
|
31
|
+
raise IsADirectoryError(self.path)
|
32
|
+
elif not self.path.is_file():
|
33
|
+
raise FileNotFoundError(self.path)
|
34
|
+
|
35
|
+
self.__contents: Optional[str] = None
|
36
|
+
self.__parsed: Optional[Parsed] = None
|
37
|
+
self.__defer_io = 0
|
38
|
+
self.__dirty = False
|
39
|
+
|
40
|
+
def __enter__(self) -> Self:
|
41
|
+
if self.__defer_io == 0:
|
42
|
+
self._read()
|
43
|
+
self.__defer_io += 1
|
44
|
+
return self
|
45
|
+
|
46
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
47
|
+
self.__defer_io -= 1
|
48
|
+
if self.__defer_io == 0 and self.__dirty:
|
49
|
+
assert self.__contents is not None
|
50
|
+
self._write(self.__contents)
|
51
|
+
assert not self.__dirty
|
52
|
+
|
53
|
+
def _read(self) -> Tuple[str, Parsed]:
|
54
|
+
if not self.__defer_io:
|
55
|
+
contents = self.path.read_text()
|
56
|
+
if contents != self.__contents:
|
57
|
+
self.__contents = contents
|
58
|
+
self.__parsed = None
|
59
|
+
|
60
|
+
assert self.__contents is not None
|
61
|
+
|
62
|
+
if self.__parsed is None:
|
63
|
+
self.__parsed = parse(self.__contents)
|
64
|
+
|
65
|
+
return self.__contents, self.__parsed
|
66
|
+
|
67
|
+
def _write(self, contents: str) -> None:
|
68
|
+
self.__contents = contents
|
69
|
+
self.__parsed = None
|
70
|
+
if not self.__defer_io:
|
71
|
+
self.path.write_text(contents)
|
72
|
+
self.__dirty = False
|
73
|
+
else:
|
74
|
+
self.__dirty = True
|
75
|
+
|
76
|
+
|
77
|
+
class FoamFile(
|
78
|
+
_FoamFileBase,
|
79
|
+
FoamDictionaryBase,
|
80
|
+
MutableMapping[
|
81
|
+
Union[str, Tuple[str, ...]], Union["FoamFile.Value", "FoamFile.Dictionary"]
|
82
|
+
],
|
83
|
+
):
|
84
|
+
"""
|
85
|
+
An OpenFOAM dictionary file.
|
86
|
+
|
87
|
+
Use as a mutable mapping (i.e., like a dict) to access and modify entries.
|
88
|
+
|
89
|
+
Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
|
90
|
+
"""
|
91
|
+
|
92
|
+
class Dictionary(
|
93
|
+
FoamDictionaryBase,
|
94
|
+
MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
|
95
|
+
):
|
96
|
+
"""
|
97
|
+
An OpenFOAM dictionary within a file as a mutable mapping.
|
98
|
+
"""
|
99
|
+
|
100
|
+
def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
|
101
|
+
self._file = _file
|
102
|
+
self._keywords = _keywords
|
103
|
+
|
104
|
+
def __getitem__(
|
105
|
+
self, keyword: str
|
106
|
+
) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
|
107
|
+
return self._file[(*self._keywords, keyword)]
|
108
|
+
|
109
|
+
def _setitem(
|
110
|
+
self,
|
111
|
+
keyword: str,
|
112
|
+
value: Any,
|
113
|
+
*,
|
114
|
+
assume_field: bool = False,
|
115
|
+
assume_dimensions: bool = False,
|
116
|
+
) -> None:
|
117
|
+
self._file._setitem(
|
118
|
+
(*self._keywords, keyword),
|
119
|
+
value,
|
120
|
+
assume_field=assume_field,
|
121
|
+
assume_dimensions=assume_dimensions,
|
122
|
+
)
|
123
|
+
|
124
|
+
def __setitem__(self, keyword: str, value: Any) -> None:
|
125
|
+
self._setitem(keyword, value)
|
126
|
+
|
127
|
+
def __delitem__(self, keyword: str) -> None:
|
128
|
+
del self._file[(*self._keywords, keyword)]
|
129
|
+
|
130
|
+
def __iter__(self) -> Iterator[str]:
|
131
|
+
return self._file._iter(tuple(self._keywords))
|
132
|
+
|
133
|
+
def __contains__(self, keyword: object) -> bool:
|
134
|
+
return (*self._keywords, keyword) in self._file
|
135
|
+
|
136
|
+
def __len__(self) -> int:
|
137
|
+
return len(list(iter(self)))
|
138
|
+
|
139
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
140
|
+
with self._file:
|
141
|
+
super().update(*args, **kwargs)
|
142
|
+
|
143
|
+
def clear(self) -> None:
|
144
|
+
with self._file:
|
145
|
+
super().clear()
|
146
|
+
|
147
|
+
def __repr__(self) -> str:
|
148
|
+
return f"{type(self).__qualname__}({self._file}, {self._keywords})"
|
149
|
+
|
150
|
+
def as_dict(self) -> FoamDictionaryBase._Dict:
|
151
|
+
"""
|
152
|
+
Return a nested dict representation of the dictionary.
|
153
|
+
"""
|
154
|
+
ret = self._file.as_dict()
|
155
|
+
|
156
|
+
for k in self._keywords:
|
157
|
+
assert isinstance(ret, dict)
|
158
|
+
v = ret[k]
|
159
|
+
assert isinstance(v, dict)
|
160
|
+
ret = v
|
161
|
+
|
162
|
+
return ret
|
163
|
+
|
164
|
+
def __getitem__(
|
165
|
+
self, keywords: Union[str, Tuple[str, ...]]
|
166
|
+
) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
|
167
|
+
if not isinstance(keywords, tuple):
|
168
|
+
keywords = (keywords,)
|
169
|
+
|
170
|
+
_, parsed = self._read()
|
171
|
+
|
172
|
+
value = get_value(parsed, keywords)
|
173
|
+
|
174
|
+
if value is None:
|
175
|
+
return FoamFile.Dictionary(self, keywords)
|
176
|
+
else:
|
177
|
+
return value
|
178
|
+
|
179
|
+
def _setitem(
|
180
|
+
self,
|
181
|
+
keywords: Union[str, Tuple[str, ...]],
|
182
|
+
value: Any,
|
183
|
+
*,
|
184
|
+
assume_field: bool = False,
|
185
|
+
assume_dimensions: bool = False,
|
186
|
+
) -> None:
|
187
|
+
if not isinstance(keywords, tuple):
|
188
|
+
keywords = (keywords,)
|
189
|
+
|
190
|
+
contents, parsed = self._read()
|
191
|
+
|
192
|
+
if isinstance(value, Mapping):
|
193
|
+
with self:
|
194
|
+
if isinstance(value, FoamDictionaryBase):
|
195
|
+
value = value.as_dict()
|
196
|
+
|
197
|
+
start, end = get_entry_locn(parsed, keywords, missing_ok=True)
|
198
|
+
|
199
|
+
self._write(
|
200
|
+
f"{contents[:start]}\n{serialize_entry(keywords[-1], {})}\n{contents[end:]}"
|
201
|
+
)
|
202
|
+
|
203
|
+
for k, v in value.items():
|
204
|
+
self[(*keywords, k)] = v
|
205
|
+
else:
|
206
|
+
start, end = get_entry_locn(parsed, keywords, missing_ok=True)
|
207
|
+
|
208
|
+
self._write(
|
209
|
+
f"{contents[:start]}\n{serialize_entry(keywords[-1], value, assume_field=assume_field, assume_dimensions=assume_dimensions)}\n{contents[end:]}"
|
210
|
+
)
|
211
|
+
|
212
|
+
def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
|
213
|
+
self._setitem(keywords, value)
|
214
|
+
|
215
|
+
def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
|
216
|
+
if not isinstance(keywords, tuple):
|
217
|
+
keywords = (keywords,)
|
218
|
+
|
219
|
+
contents, parsed = self._read()
|
220
|
+
|
221
|
+
start, end = get_entry_locn(parsed, keywords)
|
222
|
+
|
223
|
+
self._write(contents[:start] + contents[end:])
|
224
|
+
|
225
|
+
def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
|
226
|
+
if not isinstance(keywords, tuple):
|
227
|
+
keywords = (keywords,)
|
228
|
+
|
229
|
+
contents = self.path.read_text()
|
230
|
+
parsed = parse(contents)
|
231
|
+
|
232
|
+
yield from (k[-1] for k in parsed if k[:-1] == keywords)
|
233
|
+
|
234
|
+
def __iter__(self) -> Iterator[str]:
|
235
|
+
return self._iter()
|
236
|
+
|
237
|
+
def __contains__(self, keywords: object) -> bool:
|
238
|
+
if not isinstance(keywords, tuple):
|
239
|
+
keywords = (keywords,)
|
240
|
+
_, parsed = self._read()
|
241
|
+
return keywords in parsed
|
242
|
+
|
243
|
+
def __len__(self) -> int:
|
244
|
+
return len(list(iter(self)))
|
245
|
+
|
246
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
247
|
+
with self:
|
248
|
+
super().update(*args, **kwargs)
|
249
|
+
|
250
|
+
def clear(self) -> None:
|
251
|
+
with self:
|
252
|
+
super().clear()
|
253
|
+
|
254
|
+
def __fspath__(self) -> str:
|
255
|
+
return str(self.path)
|
256
|
+
|
257
|
+
def __repr__(self) -> str:
|
258
|
+
return f"{type(self).__name__}({self.path})"
|
259
|
+
|
260
|
+
def as_dict(self) -> FoamDictionaryBase._Dict:
|
261
|
+
"""
|
262
|
+
Return a nested dict representation of the file.
|
263
|
+
"""
|
264
|
+
_, parsed = self._read()
|
265
|
+
return as_dict(parsed)
|
266
|
+
|
267
|
+
|
268
|
+
class FoamFieldFile(FoamFile):
|
269
|
+
"""An OpenFOAM dictionary file representing a field as a mutable mapping."""
|
270
|
+
|
271
|
+
class BoundariesDictionary(FoamFile.Dictionary):
|
272
|
+
def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundaryDictionary":
|
273
|
+
return cast(FoamFieldFile.BoundaryDictionary, super().__getitem__(keyword))
|
274
|
+
|
275
|
+
class BoundaryDictionary(FoamFile.Dictionary):
|
276
|
+
"""An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
|
277
|
+
|
278
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
279
|
+
if key == "value":
|
280
|
+
self._setitem(key, value, assume_field=True)
|
281
|
+
else:
|
282
|
+
self._setitem(key, value)
|
283
|
+
|
284
|
+
@property
|
285
|
+
def type(self) -> str:
|
286
|
+
"""
|
287
|
+
Alias of `self["type"]`.
|
288
|
+
"""
|
289
|
+
ret = self["type"]
|
290
|
+
if not isinstance(ret, str):
|
291
|
+
raise TypeError("type is not a string")
|
292
|
+
return ret
|
293
|
+
|
294
|
+
@type.setter
|
295
|
+
def type(self, value: str) -> None:
|
296
|
+
self["type"] = value
|
297
|
+
|
298
|
+
@property
|
299
|
+
def value(
|
300
|
+
self,
|
301
|
+
) -> Union[
|
302
|
+
int,
|
303
|
+
float,
|
304
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
305
|
+
"NDArray[np.generic]",
|
306
|
+
]:
|
307
|
+
"""
|
308
|
+
Alias of `self["value"]`.
|
309
|
+
"""
|
310
|
+
ret = self["value"]
|
311
|
+
if not isinstance(ret, (int, float, Sequence)):
|
312
|
+
raise TypeError("value is not a field")
|
313
|
+
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
314
|
+
|
315
|
+
@value.setter
|
316
|
+
def value(
|
317
|
+
self,
|
318
|
+
value: Union[
|
319
|
+
int,
|
320
|
+
float,
|
321
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
322
|
+
"NDArray[np.generic]",
|
323
|
+
],
|
324
|
+
) -> None:
|
325
|
+
self["value"] = value
|
326
|
+
|
327
|
+
@value.deleter
|
328
|
+
def value(self) -> None:
|
329
|
+
del self["value"]
|
330
|
+
|
331
|
+
def __getitem__(
|
332
|
+
self, keywords: Union[str, Tuple[str, ...]]
|
333
|
+
) -> Union[FoamFile.Value, FoamFile.Dictionary]:
|
334
|
+
if not isinstance(keywords, tuple):
|
335
|
+
keywords = (keywords,)
|
336
|
+
|
337
|
+
ret = super().__getitem__(keywords)
|
338
|
+
if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.Dictionary):
|
339
|
+
if len(keywords) == 1:
|
340
|
+
ret = FoamFieldFile.BoundariesDictionary(self, keywords)
|
341
|
+
elif len(keywords) == 2:
|
342
|
+
ret = FoamFieldFile.BoundaryDictionary(self, keywords)
|
343
|
+
return ret
|
344
|
+
|
345
|
+
def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
|
346
|
+
if not isinstance(keywords, tuple):
|
347
|
+
keywords = (keywords,)
|
348
|
+
|
349
|
+
if keywords == ("internalField",):
|
350
|
+
self._setitem(keywords, value, assume_field=True)
|
351
|
+
elif keywords == ("dimensions",):
|
352
|
+
self._setitem(keywords, value, assume_dimensions=True)
|
353
|
+
else:
|
354
|
+
self._setitem(keywords, value)
|
355
|
+
|
356
|
+
@property
|
357
|
+
def dimensions(self) -> FoamFile.DimensionSet:
|
358
|
+
"""
|
359
|
+
Alias of `self["dimensions"]`.
|
360
|
+
"""
|
361
|
+
ret = self["dimensions"]
|
362
|
+
if not isinstance(ret, FoamFile.DimensionSet):
|
363
|
+
raise TypeError("dimensions is not a DimensionSet")
|
364
|
+
return ret
|
365
|
+
|
366
|
+
@dimensions.setter
|
367
|
+
def dimensions(
|
368
|
+
self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
|
369
|
+
) -> None:
|
370
|
+
self["dimensions"] = value
|
371
|
+
|
372
|
+
@property
|
373
|
+
def internal_field(
|
374
|
+
self,
|
375
|
+
) -> Union[
|
376
|
+
int,
|
377
|
+
float,
|
378
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
379
|
+
"NDArray[np.generic]",
|
380
|
+
]:
|
381
|
+
"""
|
382
|
+
Alias of `self["internalField"]`.
|
383
|
+
"""
|
384
|
+
ret = self["internalField"]
|
385
|
+
if not isinstance(ret, (int, float, Sequence)):
|
386
|
+
raise TypeError("internalField is not a field")
|
387
|
+
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
388
|
+
|
389
|
+
@internal_field.setter
|
390
|
+
def internal_field(
|
391
|
+
self,
|
392
|
+
value: Union[
|
393
|
+
int,
|
394
|
+
float,
|
395
|
+
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
396
|
+
"NDArray[np.generic]",
|
397
|
+
],
|
398
|
+
) -> None:
|
399
|
+
self["internalField"] = value
|
400
|
+
|
401
|
+
@property
|
402
|
+
def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
|
403
|
+
"""
|
404
|
+
Alias of `self["boundaryField"]`.
|
405
|
+
"""
|
406
|
+
ret = self["boundaryField"]
|
407
|
+
if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
|
408
|
+
assert not isinstance(ret, FoamFile.Dictionary)
|
409
|
+
raise TypeError("boundaryField is not a dictionary")
|
410
|
+
return ret
|
@@ -0,0 +1,177 @@
|
|
1
|
+
from typing import Mapping, MutableMapping, Optional, Sequence, Tuple
|
2
|
+
|
3
|
+
from pyparsing import (
|
4
|
+
Dict,
|
5
|
+
Forward,
|
6
|
+
Group,
|
7
|
+
Keyword,
|
8
|
+
LineEnd,
|
9
|
+
Literal,
|
10
|
+
Located,
|
11
|
+
Opt,
|
12
|
+
ParseResults,
|
13
|
+
ParserElement,
|
14
|
+
QuotedString,
|
15
|
+
Word,
|
16
|
+
c_style_comment,
|
17
|
+
common,
|
18
|
+
cpp_style_comment,
|
19
|
+
identbodychars,
|
20
|
+
printables,
|
21
|
+
)
|
22
|
+
|
23
|
+
from ._base import FoamDictionaryBase
|
24
|
+
|
25
|
+
_YES = Keyword("yes").set_parse_action(lambda: True)
|
26
|
+
_NO = Keyword("no").set_parse_action(lambda: False)
|
27
|
+
_DIMENSIONS = (
|
28
|
+
Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
|
29
|
+
).set_parse_action(lambda tks: FoamDictionaryBase.DimensionSet(*tks))
|
30
|
+
|
31
|
+
|
32
|
+
def _list_of(elem: ParserElement) -> ParserElement:
|
33
|
+
return Opt(
|
34
|
+
Literal("List") + Literal("<") + common.identifier + Literal(">")
|
35
|
+
).suppress() + (
|
36
|
+
(
|
37
|
+
Opt(common.integer).suppress()
|
38
|
+
+ (
|
39
|
+
Literal("(").suppress()
|
40
|
+
+ Group((elem)[...], aslist=True)
|
41
|
+
+ Literal(")").suppress()
|
42
|
+
)
|
43
|
+
)
|
44
|
+
| (
|
45
|
+
common.integer + Literal("{").suppress() + elem + Literal("}").suppress()
|
46
|
+
).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
_TENSOR = _list_of(common.number) | common.number
|
51
|
+
_IDENTIFIER = Word(identbodychars + "$", printables.replace(";", ""))
|
52
|
+
_DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
|
53
|
+
lambda tks: FoamDictionaryBase.Dimensioned(*reversed(tks.as_list()))
|
54
|
+
)
|
55
|
+
_FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
|
56
|
+
Keyword("nonuniform").suppress() + _list_of(_TENSOR)
|
57
|
+
)
|
58
|
+
_TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
|
59
|
+
_ITEM = Forward()
|
60
|
+
_LIST = _list_of(_ITEM)
|
61
|
+
_ITEM <<= (
|
62
|
+
_FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN
|
63
|
+
)
|
64
|
+
_TOKENS = (
|
65
|
+
QuotedString('"', unquote_results=False) | Word(printables.replace(";", ""))
|
66
|
+
)[2, ...].set_parse_action(lambda tks: " ".join(tks))
|
67
|
+
|
68
|
+
_VALUE = _ITEM ^ _TOKENS
|
69
|
+
|
70
|
+
_ENTRY = Forward()
|
71
|
+
_DICTIONARY = Dict(Group(_ENTRY)[...])
|
72
|
+
_ENTRY <<= Located(
|
73
|
+
_TOKEN
|
74
|
+
+ (
|
75
|
+
(Literal("{").suppress() + _DICTIONARY + Literal("}").suppress())
|
76
|
+
| (Opt(_VALUE, default="") + Literal(";").suppress())
|
77
|
+
)
|
78
|
+
)
|
79
|
+
_FILE = (
|
80
|
+
_DICTIONARY.ignore(c_style_comment)
|
81
|
+
.ignore(cpp_style_comment)
|
82
|
+
.ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
|
83
|
+
)
|
84
|
+
|
85
|
+
Parsed = Mapping[Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]]
|
86
|
+
|
87
|
+
|
88
|
+
def _flatten_result(
|
89
|
+
parse_result: ParseResults, *, _keywords: Sequence[str] = ()
|
90
|
+
) -> Parsed:
|
91
|
+
ret: MutableMapping[
|
92
|
+
Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
|
93
|
+
] = {}
|
94
|
+
start = parse_result.locn_start
|
95
|
+
assert isinstance(start, int)
|
96
|
+
item = parse_result.value
|
97
|
+
assert isinstance(item, Sequence)
|
98
|
+
end = parse_result.locn_end
|
99
|
+
assert isinstance(end, int)
|
100
|
+
key, *values = item
|
101
|
+
assert isinstance(key, str)
|
102
|
+
ret[(*_keywords, key)] = (start, None, end)
|
103
|
+
for value in values:
|
104
|
+
if isinstance(value, ParseResults):
|
105
|
+
ret.update(_flatten_result(value, _keywords=(*_keywords, key)))
|
106
|
+
else:
|
107
|
+
ret[(*_keywords, key)] = (start, value, end)
|
108
|
+
return ret
|
109
|
+
|
110
|
+
|
111
|
+
def parse(
|
112
|
+
contents: str,
|
113
|
+
) -> Parsed:
|
114
|
+
parse_results = _FILE.parse_string(contents, parse_all=True)
|
115
|
+
ret: MutableMapping[
|
116
|
+
Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
|
117
|
+
] = {}
|
118
|
+
for parse_result in parse_results:
|
119
|
+
ret.update(_flatten_result(parse_result))
|
120
|
+
return ret
|
121
|
+
|
122
|
+
|
123
|
+
def get_value(
|
124
|
+
parsed: Parsed,
|
125
|
+
keywords: Tuple[str, ...],
|
126
|
+
) -> Optional[FoamDictionaryBase.Value]:
|
127
|
+
"""
|
128
|
+
Value of an entry.
|
129
|
+
"""
|
130
|
+
_, value, _ = parsed[keywords]
|
131
|
+
return value
|
132
|
+
|
133
|
+
|
134
|
+
def get_entry_locn(
|
135
|
+
parsed: Parsed,
|
136
|
+
keywords: Tuple[str, ...],
|
137
|
+
*,
|
138
|
+
missing_ok: bool = False,
|
139
|
+
) -> Tuple[int, int]:
|
140
|
+
"""
|
141
|
+
Location of an entry or where it should be inserted.
|
142
|
+
"""
|
143
|
+
try:
|
144
|
+
start, _, end = parsed[keywords]
|
145
|
+
except KeyError:
|
146
|
+
if missing_ok:
|
147
|
+
if len(keywords) > 1:
|
148
|
+
_, _, end = parsed[keywords[:-1]]
|
149
|
+
end -= 1
|
150
|
+
else:
|
151
|
+
end = -1
|
152
|
+
|
153
|
+
start = end
|
154
|
+
else:
|
155
|
+
raise
|
156
|
+
|
157
|
+
return start, end
|
158
|
+
|
159
|
+
|
160
|
+
def as_dict(parsed: Parsed) -> FoamDictionaryBase._Dict:
|
161
|
+
"""
|
162
|
+
Return a nested dict representation of the file.
|
163
|
+
"""
|
164
|
+
ret: FoamDictionaryBase._Dict = {}
|
165
|
+
for keywords, (_, value, _) in parsed.items():
|
166
|
+
|
167
|
+
r = ret
|
168
|
+
for k in keywords[:-1]:
|
169
|
+
assert isinstance(r, dict)
|
170
|
+
v = r[k]
|
171
|
+
assert isinstance(v, dict)
|
172
|
+
r = v
|
173
|
+
|
174
|
+
assert isinstance(r, dict)
|
175
|
+
r[keywords[-1]] = {} if value is None else value
|
176
|
+
|
177
|
+
return ret
|