-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathf06csv_to_magic.py
More file actions
275 lines (224 loc) · 9.1 KB
/
f06csv_to_magic.py
File metadata and controls
275 lines (224 loc) · 9.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# Made by Claude
"""Convert f06csv filter arguments to an f06magic TOML extraction block.
f06csv flags handled:
-b / --blocks CsvBlockId values -> block / blocks
-g / --gids grid point IDs -> node / nodes
-e / --eids element IDs -> element / elements
-t / --etypes element types -> element_type / element_types
-s / --subcases subcase IDs -> subcase / subcases
-c / --cols column indices -> column / columns
Flags that are purely CSV formatting (-o, -H, -d, --tab, --crlf, -v, --align,
etc.) have no equivalent in an f06magic extraction and are silently ignored.
"""
from __future__ import annotations
import re
import shlex
from typing import Any
# ---------------------------------------------------------------------------
# Block ID -> canonical name mapping
# Derived from CsvBlockId in nas_csv/src/layout.rs.
# Keys are every accepted alias (including the numeric shorthand string).
# ---------------------------------------------------------------------------
_BLOCK_NAME: dict[str, str] = {}
_BLOCK_ALIASES: list[tuple[str, list[str]]] = [
("metadata", ["0", "sol", "sol_info", "info"]),
("displacements", ["1", "disp", "displs", "displacements"]),
("stresses", ["2", "stresses"]),
("strains", ["3", "strains"]),
("eng_forces", ["4", "engforces", "eng_forces"]),
("grid_point_forces", ["5", "gpfb", "gpfor", "gpforces",
"grid_point_forces", "grid_point_force_balance"]),
("applied", ["6", "applied"]),
("spcforces", ["7", "spcf", "spcforces"]),
("eigenvectors", ["8", "eigenvectors"]),
("eigenvalues", ["9", "eigenvalues"]),
]
for _canonical, _aliases in _BLOCK_ALIASES:
for _alias in _aliases:
_BLOCK_NAME[_alias.lower()] = _canonical
def _resolve_block(raw: str) -> str:
"""Return the canonical block name for a raw f06csv block token.
Accepts numeric IDs (``"1"``), short aliases (``"disp"``), and full
names (``"displacements"``). Unknown values are returned unchanged.
"""
return _BLOCK_NAME.get(raw.lower(), raw)
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _toml_value(values: list[Any]) -> str:
"""Render a list of values as a compact TOML value.
* Empty list -> should not be called (caller skips the key).
* Single item -> bare scalar (e.g. "displacements" or 1)
* Many items -> inline array (e.g. [1, 3, 7])
"""
def _fmt(v: Any) -> str:
if isinstance(v, str):
return f'"{v}"'
return str(v)
if len(values) == 1:
return _fmt(values[0])
return "[" + ", ".join(_fmt(v) for v in values) + "]"
def _split_csv(raw: str) -> list[str]:
"""Split a comma-separated token, stripping whitespace."""
return [p.strip() for p in raw.split(",") if p.strip()]
# ---------------------------------------------------------------------------
# Argument parser (lightweight, no argparse dependency)
# ---------------------------------------------------------------------------
# Maps short flag -> (long key used in result dict, value type)
_FLAG_MAP: dict[str, tuple[str, str]] = {
"b": ("blocks", "block"),
"g": ("gids", "int"),
"e": ("eids", "int"),
"t": ("etypes", "str"),
"s": ("subcases", "int"),
"c": ("cols", "int"),
# long-form aliases
"blocks": ("blocks", "block"),
"gids": ("gids", "int"),
"eids": ("eids", "int"),
"etypes": ("etypes", "str"),
"subcases": ("subcases", "int"),
"cols": ("cols", "int"),
}
# Flags that take no value (boolean switches) – ignored for TOML output
_BOOL_FLAGS = {"H", "headers", "tab", "crlf", "v", "verbose"}
def _parse_args(args: list[str]) -> dict[str, list[Any]]:
"""Parse a flat list of f06csv tokens into a dict of filter lists.
Accepts both ``-b=displacements`` and ``-b displacements`` styles,
as well as comma-separated values in a single token (``-g=1,3,7``).
"""
result: dict[str, list[Any]] = {
"blocks": [], "gids": [], "eids": [],
"etypes": [], "subcases": [], "cols": [],
}
i = 0
while i < len(args):
token = args[i]
# ---- long flag: --key=value or --key value -------------------------
m = re.fullmatch(r"--([a-zA-Z_-]+)(?:=(.*))?", token)
if m:
key_raw, val_inline = m.group(1), m.group(2)
key_raw = key_raw.replace("-", "_")
if key_raw in _BOOL_FLAGS:
i += 1
continue
if key_raw not in _FLAG_MAP:
i += 1
continue
dest, vtype = _FLAG_MAP[key_raw]
if val_inline is None:
i += 1
if i < len(args) and not args[i].startswith("-"):
val_inline = args[i]
else:
i += 1
continue
for part in _split_csv(val_inline):
if vtype == "int":
result[dest].append(int(part))
elif vtype == "block":
result[dest].append(_resolve_block(part))
else:
result[dest].append(part)
i += 1
continue
# ---- short flag: -k=value or -k value ------------------------------
m = re.fullmatch(r"-([a-zA-Z])(?:=(.*))?", token)
if m:
key_raw, val_inline = m.group(1), m.group(2)
if key_raw in _BOOL_FLAGS:
i += 1
continue
if key_raw not in _FLAG_MAP:
i += 1
continue
dest, vtype = _FLAG_MAP[key_raw]
if val_inline is None:
i += 1
if i < len(args) and not args[i].startswith("-"):
val_inline = args[i]
else:
i += 1
continue
for part in _split_csv(val_inline):
if vtype == "int":
result[dest].append(int(part))
elif vtype == "block":
result[dest].append(_resolve_block(part))
else:
result[dest].append(part)
i += 1
continue
# positional / unrecognised token – skip
i += 1
return result
# ---------------------------------------------------------------------------
# Mapping from f06csv filter keys to f06magic TOML keys
# ---------------------------------------------------------------------------
# (filter_key, singular_toml_key, plural_toml_key)
_FIELD_MAP = [
("blocks", "block", "blocks"),
("gids", "node", "nodes"),
("eids", "element", "elements"),
("etypes", "element_type", "element_types"),
("subcases", "subcase", "subcases"),
("cols", "column", "columns"),
]
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def f06csv_args_to_magic(
args: list[str] | str,
*,
name: str = "extraction",
) -> str:
"""Convert f06csv filter arguments to an f06magic TOML extraction block.
Parameters
----------
args:
Either a list of tokens (e.g. ``["-b", "displacements", "-g=1,3,7"]``)
or a single shell-style string (e.g. ``"-b displacements -g 1,3,7"``).
name:
The value to use for the ``name`` key in the extraction block.
Returns
-------
str
A TOML ``[[extraction]]`` block as a string.
Examples
--------
>>> print(f06csv_args_to_magic("-b displacements -g 1,3,7"))
[[extraction]]
name = "my_extraction"
block = "displacements"
nodes = [1, 3, 7]
>>> print(f06csv_args_to_magic(["-b=1", "-s", "2", "-e", "10,20"]))
[[extraction]]
name = "my_extraction"
block = 1
subcase = 2
elements = [10, 20]
"""
if isinstance(args, str):
args = shlex.split(args)
filters = _parse_args(args)
lines: list[str] = ["[[extraction]]", f'name = "{name}"']
for filter_key, singular, plural in _FIELD_MAP:
values = filters[filter_key]
if not values:
continue
toml_key = singular if len(values) == 1 else plural
lines.append(f"{toml_key} = {_toml_value(values)}")
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI convenience
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import sys
# Everything after the script name is treated as f06csv args.
# Optionally pass --name=<name> as the very first argument.
argv = sys.argv[1:]
extraction_name = "extraction"
if argv and argv[0].startswith("--name="):
extraction_name = argv[0].split("=", 1)[1]
argv = argv[1:]
print(f06csv_args_to_magic(argv, name=extraction_name))