foamlib 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {foamlib-0.2.2 → foamlib-0.2.3}/PKG-INFO +1 -1
  2. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib/__init__.py +3 -2
  3. foamlib-0.2.3/foamlib/_dictionaries/__init__.py +8 -0
  4. foamlib-0.2.3/foamlib/_dictionaries/_base.py +43 -0
  5. foamlib-0.2.3/foamlib/_dictionaries/_files.py +382 -0
  6. foamlib-0.2.3/foamlib/_dictionaries/_parsing.py +177 -0
  7. foamlib-0.2.3/foamlib/_dictionaries/_serialization.py +103 -0
  8. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib.egg-info/PKG-INFO +1 -1
  9. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib.egg-info/SOURCES.txt +5 -1
  10. {foamlib-0.2.2 → foamlib-0.2.3}/tests/test_dictionaries.py +27 -33
  11. foamlib-0.2.2/foamlib/_dictionaries.py +0 -491
  12. {foamlib-0.2.2 → foamlib-0.2.3}/LICENSE.txt +0 -0
  13. {foamlib-0.2.2 → foamlib-0.2.3}/README.md +0 -0
  14. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib/_cases.py +0 -0
  15. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib/_subprocesses.py +0 -0
  16. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib/py.typed +0 -0
  17. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib.egg-info/dependency_links.txt +0 -0
  18. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib.egg-info/requires.txt +0 -0
  19. {foamlib-0.2.2 → foamlib-0.2.3}/foamlib.egg-info/top_level.txt +0 -0
  20. {foamlib-0.2.2 → foamlib-0.2.3}/pyproject.toml +0 -0
  21. {foamlib-0.2.2 → foamlib-0.2.3}/setup.cfg +0 -0
  22. {foamlib-0.2.2 → foamlib-0.2.3}/tests/test_basic.py +0 -0
  23. {foamlib-0.2.2 → foamlib-0.2.3}/tests/test_flange.py +0 -0
  24. {foamlib-0.2.2 → foamlib-0.2.3}/tests/test_flange_async.py +0 -0
  25. {foamlib-0.2.2 → foamlib-0.2.3}/tests/test_pitz.py +0 -0
  26. {foamlib-0.2.2 → foamlib-0.2.3}/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.2
3
+ Version: 0.2.3
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
@@ -1,7 +1,7 @@
1
- __version__ = "0.2.2"
1
+ __version__ = "0.2.3"
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,8 @@
1
+ from ._files import FoamFile, FoamFieldFile
2
+ from ._base import FoamDictionaryBase
3
+
4
+ __all__ = [
5
+ "FoamFile",
6
+ "FoamFieldFile",
7
+ "FoamDictionaryBase",
8
+ ]
@@ -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,382 @@
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 ._base import FoamDictionaryBase
15
+ from ._parsing import Parsed, as_dict, get_entry_locn, get_value, parse
16
+ from ._serialization import serialize_value
17
+
18
+ try:
19
+ import numpy as np
20
+ from numpy.typing import NDArray
21
+ except ModuleNotFoundError:
22
+ pass
23
+
24
+
25
+ class FoamFile(
26
+ FoamDictionaryBase,
27
+ MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
28
+ ):
29
+ """
30
+ An OpenFOAM dictionary file.
31
+
32
+ Use as a mutable mapping (i.e., like a dict) to access and modify entries.
33
+
34
+ Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
35
+ """
36
+
37
+ class Dictionary(
38
+ FoamDictionaryBase,
39
+ MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
40
+ ):
41
+ """
42
+ An OpenFOAM dictionary within a file as a mutable mapping.
43
+ """
44
+
45
+ def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
46
+ self._file = _file
47
+ self._keywords = _keywords
48
+
49
+ def __getitem__(
50
+ self, keyword: str
51
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
52
+ return self._file[(*self._keywords, keyword)]
53
+
54
+ def _setitem(
55
+ self,
56
+ keyword: str,
57
+ value: Any,
58
+ *,
59
+ assume_field: bool = False,
60
+ assume_dimensions: bool = False,
61
+ ) -> None:
62
+ self._file._setitem(
63
+ (*self._keywords, keyword),
64
+ value,
65
+ assume_field=assume_field,
66
+ assume_dimensions=assume_dimensions,
67
+ )
68
+
69
+ def __setitem__(self, keyword: str, value: Any) -> None:
70
+ self._setitem(keyword, value)
71
+
72
+ def __delitem__(self, keyword: str) -> None:
73
+ del self._file[(*self._keywords, keyword)]
74
+
75
+ def __iter__(self) -> Iterator[str]:
76
+ return self._file._iter(tuple(self._keywords))
77
+
78
+ def __len__(self) -> int:
79
+ return len(list(iter(self)))
80
+
81
+ def __repr__(self) -> str:
82
+ return f"{type(self).__qualname__}({self._file}, {self._keywords})"
83
+
84
+ def as_dict(self) -> FoamDictionaryBase._Dict:
85
+ """
86
+ Return a nested dict representation of the dictionary.
87
+ """
88
+ ret = self._file.as_dict()
89
+
90
+ for k in self._keywords:
91
+ assert isinstance(ret, dict)
92
+ v = ret[k]
93
+ assert isinstance(v, dict)
94
+ ret = v
95
+
96
+ return ret
97
+
98
+ def __init__(self, path: Union[str, Path]) -> None:
99
+ self.path = Path(path).absolute()
100
+ if self.path.is_dir():
101
+ raise IsADirectoryError(self.path)
102
+ elif not self.path.is_file():
103
+ raise FileNotFoundError(self.path)
104
+
105
+ self._contents: Optional[str] = None
106
+ self._parsed: Optional[Parsed] = None
107
+ self._defer_io = 0
108
+ self._dirty = False
109
+
110
+ def __enter__(self) -> "FoamFile":
111
+ if self._defer_io == 0:
112
+ self._read()
113
+ self._defer_io += 1
114
+ return self
115
+
116
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
117
+ self._defer_io -= 1
118
+ if self._defer_io == 0 and self._dirty:
119
+ assert self._contents is not None
120
+ self._write(self._contents)
121
+ assert not self._dirty
122
+
123
+ def _read(self) -> Tuple[str, Parsed]:
124
+ if not self._defer_io:
125
+ contents = self.path.read_text()
126
+ if contents != self._contents:
127
+ self._contents = contents
128
+ self._parsed = None
129
+
130
+ assert self._contents is not None
131
+
132
+ if self._parsed is None:
133
+ self._parsed = parse(self._contents)
134
+
135
+ return self._contents, self._parsed
136
+
137
+ def _write(self, contents: str) -> None:
138
+ self._contents = contents
139
+ self._parsed = None
140
+ if not self._defer_io:
141
+ self.path.write_text(contents)
142
+ self._dirty = False
143
+ else:
144
+ self._dirty = True
145
+
146
+ def __getitem__(
147
+ self, keywords: Union[str, Tuple[str, ...]]
148
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
149
+ if not isinstance(keywords, tuple):
150
+ keywords = (keywords,)
151
+
152
+ _, parsed = self._read()
153
+
154
+ value = get_value(parsed, keywords)
155
+
156
+ if value is None:
157
+ return FoamFile.Dictionary(self, keywords)
158
+ else:
159
+ return value
160
+
161
+ def _setitem(
162
+ self,
163
+ keywords: Union[str, Tuple[str, ...]],
164
+ value: Any,
165
+ *,
166
+ assume_field: bool = False,
167
+ assume_dimensions: bool = False,
168
+ ) -> None:
169
+ if not isinstance(keywords, tuple):
170
+ keywords = (keywords,)
171
+
172
+ contents, parsed = self._read()
173
+
174
+ if isinstance(value, Mapping):
175
+ with self:
176
+ if isinstance(value, FoamDictionaryBase):
177
+ value = value.as_dict()
178
+
179
+ start, end = get_entry_locn(parsed, keywords, missing_ok=True)
180
+
181
+ self._write(
182
+ f"{contents[:start]} {keywords[-1]} {{\n}}\n {contents[end:]}"
183
+ )
184
+
185
+ for k, v in value.items():
186
+ self[(*keywords, k)] = v
187
+ else:
188
+ start, end = get_entry_locn(parsed, keywords, missing_ok=True)
189
+
190
+ value = serialize_value(
191
+ value, assume_field=assume_field, assume_dimensions=assume_dimensions
192
+ )
193
+
194
+ self._write(
195
+ f"{contents[:start]} {keywords[-1]} {value};\n {contents[end:]}"
196
+ )
197
+
198
+ def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
199
+ self._setitem(keywords, value)
200
+
201
+ def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
202
+ if not isinstance(keywords, tuple):
203
+ keywords = (keywords,)
204
+
205
+ contents, parsed = self._read()
206
+
207
+ start, end = get_entry_locn(parsed, keywords)
208
+
209
+ self._write(contents[:start] + contents[end:])
210
+
211
+ def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
212
+ if not isinstance(keywords, tuple):
213
+ keywords = (keywords,)
214
+
215
+ contents = self.path.read_text()
216
+ parsed = parse(contents)
217
+
218
+ yield from (k[-1] for k in parsed if k[:-1] == keywords)
219
+
220
+ def __iter__(self) -> Iterator[str]:
221
+ return self._iter()
222
+
223
+ def __len__(self) -> int:
224
+ return len(list(iter(self)))
225
+
226
+ def __fspath__(self) -> str:
227
+ return str(self.path)
228
+
229
+ def __repr__(self) -> str:
230
+ return f"{type(self).__name__}({self.path})"
231
+
232
+ def as_dict(self) -> FoamDictionaryBase._Dict:
233
+ """
234
+ Return a nested dict representation of the file.
235
+ """
236
+ _, parsed = self._read()
237
+ return as_dict(parsed)
238
+
239
+
240
+ class FoamFieldFile(FoamFile):
241
+ """An OpenFOAM dictionary file representing a field as a mutable mapping."""
242
+
243
+ class BoundariesDictionary(FoamFile.Dictionary):
244
+ def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundaryDictionary":
245
+ return cast(FoamFieldFile.BoundaryDictionary, super().__getitem__(keyword))
246
+
247
+ class BoundaryDictionary(FoamFile.Dictionary):
248
+ """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
249
+
250
+ def __setitem__(self, key: str, value: Any) -> None:
251
+ if key == "value":
252
+ self._setitem(key, value, assume_field=True)
253
+ else:
254
+ self._setitem(key, value)
255
+
256
+ @property
257
+ def type(self) -> str:
258
+ """
259
+ Alias of `self["type"]`.
260
+ """
261
+ ret = self["type"]
262
+ if not isinstance(ret, str):
263
+ raise TypeError("type is not a string")
264
+ return ret
265
+
266
+ @type.setter
267
+ def type(self, value: str) -> None:
268
+ self["type"] = value
269
+
270
+ @property
271
+ def value(
272
+ self,
273
+ ) -> Union[
274
+ int,
275
+ float,
276
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
277
+ "NDArray[np.generic]",
278
+ ]:
279
+ """
280
+ Alias of `self["value"]`.
281
+ """
282
+ ret = self["value"]
283
+ if not isinstance(ret, (int, float, Sequence)):
284
+ raise TypeError("value is not a field")
285
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
286
+
287
+ @value.setter
288
+ def value(
289
+ self,
290
+ value: Union[
291
+ int,
292
+ float,
293
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
294
+ "NDArray[np.generic]",
295
+ ],
296
+ ) -> None:
297
+ self["value"] = value
298
+
299
+ @value.deleter
300
+ def value(self) -> None:
301
+ del self["value"]
302
+
303
+ def __getitem__(
304
+ self, keywords: Union[str, Tuple[str, ...]]
305
+ ) -> Union[FoamFile.Value, FoamFile.Dictionary]:
306
+ if not isinstance(keywords, tuple):
307
+ keywords = (keywords,)
308
+
309
+ ret = super().__getitem__(keywords)
310
+ if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.Dictionary):
311
+ if len(keywords) == 1:
312
+ ret = FoamFieldFile.BoundariesDictionary(self, keywords)
313
+ elif len(keywords) == 2:
314
+ ret = FoamFieldFile.BoundaryDictionary(self, keywords)
315
+ return ret
316
+
317
+ def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
318
+ if not isinstance(keywords, tuple):
319
+ keywords = (keywords,)
320
+
321
+ if keywords == ("internalField",):
322
+ self._setitem(keywords, value, assume_field=True)
323
+ elif keywords == ("dimensions",):
324
+ self._setitem(keywords, value, assume_dimensions=True)
325
+ else:
326
+ self._setitem(keywords, value)
327
+
328
+ @property
329
+ def dimensions(self) -> FoamFile.DimensionSet:
330
+ """
331
+ Alias of `self["dimensions"]`.
332
+ """
333
+ ret = self["dimensions"]
334
+ if not isinstance(ret, FoamFile.DimensionSet):
335
+ raise TypeError("dimensions is not a DimensionSet")
336
+ return ret
337
+
338
+ @dimensions.setter
339
+ def dimensions(
340
+ self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
341
+ ) -> None:
342
+ self["dimensions"] = value
343
+
344
+ @property
345
+ def internal_field(
346
+ self,
347
+ ) -> Union[
348
+ int,
349
+ float,
350
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
351
+ "NDArray[np.generic]",
352
+ ]:
353
+ """
354
+ Alias of `self["internalField"]`.
355
+ """
356
+ ret = self["internalField"]
357
+ if not isinstance(ret, (int, float, Sequence)):
358
+ raise TypeError("internalField is not a field")
359
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
360
+
361
+ @internal_field.setter
362
+ def internal_field(
363
+ self,
364
+ value: Union[
365
+ int,
366
+ float,
367
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
368
+ "NDArray[np.generic]",
369
+ ],
370
+ ) -> None:
371
+ self["internalField"] = value
372
+
373
+ @property
374
+ def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
375
+ """
376
+ Alias of `self["boundaryField"]`.
377
+ """
378
+ ret = self["boundaryField"]
379
+ if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
380
+ assert not isinstance(ret, FoamFile.Dictionary)
381
+ raise TypeError("boundaryField is not a dictionary")
382
+ 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