fmu-manipulation-toolbox 1.8.4.3b0__py3-none-any.whl → 1.9__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.
- fmu_manipulation_toolbox/__init__.py +0 -1
- fmu_manipulation_toolbox/__main__.py +1 -1
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/assembly.py +21 -12
- fmu_manipulation_toolbox/checker.py +11 -8
- fmu_manipulation_toolbox/cli/__init__.py +0 -0
- fmu_manipulation_toolbox/cli/fmucontainer.py +105 -0
- fmu_manipulation_toolbox/cli/fmusplit.py +48 -0
- fmu_manipulation_toolbox/cli/fmutool.py +127 -0
- fmu_manipulation_toolbox/cli/utils.py +36 -0
- fmu_manipulation_toolbox/container.py +534 -247
- fmu_manipulation_toolbox/gui.py +47 -55
- fmu_manipulation_toolbox/gui_style.py +8 -0
- fmu_manipulation_toolbox/help.py +3 -0
- fmu_manipulation_toolbox/operations.py +251 -179
- fmu_manipulation_toolbox/remoting.py +107 -0
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
- fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- fmu_manipulation_toolbox/split.py +67 -34
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/RECORD +34 -27
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/top_level.txt +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +1 @@
|
|
|
1
|
-
'V1.
|
|
1
|
+
'V1.9'
|
|
@@ -37,7 +37,7 @@ class Connection:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class AssemblyNode:
|
|
40
|
-
def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False,
|
|
40
|
+
def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False, sequential=False,
|
|
41
41
|
auto_link=True, auto_input=True, auto_output=True, auto_parameter=False, auto_local=False):
|
|
42
42
|
self.name = name
|
|
43
43
|
if step_size:
|
|
@@ -50,6 +50,7 @@ class AssemblyNode:
|
|
|
50
50
|
self.step_size = None
|
|
51
51
|
self.mt = mt
|
|
52
52
|
self.profiling = profiling
|
|
53
|
+
self.sequential = sequential
|
|
53
54
|
self.auto_link = auto_link
|
|
54
55
|
self.auto_input = auto_input
|
|
55
56
|
self.auto_output = auto_output
|
|
@@ -100,12 +101,13 @@ class AssemblyNode:
|
|
|
100
101
|
def add_start_value(self, fmu_filename: str, port_name: str, value: str):
|
|
101
102
|
self.start_values[Port(fmu_filename, port_name)] = value
|
|
102
103
|
|
|
103
|
-
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
104
|
+
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None, fmi_version=2):
|
|
104
105
|
for node in self.children.values():
|
|
105
|
-
node.make_fmu(fmu_directory, debug=debug)
|
|
106
|
+
node.make_fmu(fmu_directory, debug=debug, fmi_version=fmi_version)
|
|
106
107
|
|
|
107
108
|
identifier = str(Path(self.name).stem)
|
|
108
|
-
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname
|
|
109
|
+
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname,
|
|
110
|
+
fmi_version=fmi_version)
|
|
109
111
|
|
|
110
112
|
for fmu_name in self.fmu_names_list:
|
|
111
113
|
container.get_fmu(fmu_name)
|
|
@@ -138,7 +140,8 @@ class AssemblyNode:
|
|
|
138
140
|
for link_rule in wired.rule_link:
|
|
139
141
|
self.add_link(link_rule[0], link_rule[1], link_rule[2], link_rule[3])
|
|
140
142
|
|
|
141
|
-
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling,
|
|
143
|
+
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, sequential=self.sequential,
|
|
144
|
+
debug=debug)
|
|
142
145
|
|
|
143
146
|
for node in self.children.values():
|
|
144
147
|
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
@@ -216,7 +219,7 @@ class AssemblyError(Exception):
|
|
|
216
219
|
|
|
217
220
|
|
|
218
221
|
class Assembly:
|
|
219
|
-
def __init__(self, filename: str, step_size=None, auto_link=True, auto_input=True, debug=False,
|
|
222
|
+
def __init__(self, filename: str, step_size=None, auto_link=True, auto_input=True, debug=False, sequential=False,
|
|
220
223
|
auto_output=True, mt=False, profiling=False, fmu_directory: Path = Path("."), auto_parameter=False,
|
|
221
224
|
auto_local=False):
|
|
222
225
|
self.filename = Path(filename)
|
|
@@ -228,6 +231,7 @@ class Assembly:
|
|
|
228
231
|
self.default_auto_parameter = auto_parameter
|
|
229
232
|
self.default_auto_local = auto_local
|
|
230
233
|
self.default_mt = mt
|
|
234
|
+
self.default_sequential = sequential
|
|
231
235
|
self.default_profiling = profiling
|
|
232
236
|
self.fmu_directory = fmu_directory
|
|
233
237
|
self.transient_filenames: List[Path] = []
|
|
@@ -238,7 +242,7 @@ class Assembly:
|
|
|
238
242
|
|
|
239
243
|
self.input_pathname = fmu_directory / self.filename
|
|
240
244
|
self.description_pathname = self.input_pathname # For inclusion in FMU
|
|
241
|
-
self.root = None
|
|
245
|
+
self.root: Optional[AssemblyNode] = None
|
|
242
246
|
self.read()
|
|
243
247
|
|
|
244
248
|
def add_transient_file(self, filename: str):
|
|
@@ -283,8 +287,9 @@ class Assembly:
|
|
|
283
287
|
name = str(self.filename.with_suffix(".fmu"))
|
|
284
288
|
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
285
289
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
286
|
-
|
|
287
|
-
|
|
290
|
+
sequential=self.default_sequential, auto_input=self.default_auto_input,
|
|
291
|
+
auto_output=self.default_auto_output, auto_parameter=self.default_auto_parameter,
|
|
292
|
+
auto_local=self.default_auto_local)
|
|
288
293
|
|
|
289
294
|
with open(self.input_pathname) as file:
|
|
290
295
|
reader = csv.reader(file, delimiter=';')
|
|
@@ -385,6 +390,7 @@ class Assembly:
|
|
|
385
390
|
name = data.get("name", None) # 1
|
|
386
391
|
mt = data.get("mt", self.default_mt) # 2
|
|
387
392
|
profiling = data.get("profiling", self.default_profiling) # 3
|
|
393
|
+
sequential = data.get("sequential", self.default_sequential) # 3b
|
|
388
394
|
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
389
395
|
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
390
396
|
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
@@ -393,11 +399,12 @@ class Assembly:
|
|
|
393
399
|
step_size = data.get("step_size", self.default_step_size) # 7
|
|
394
400
|
|
|
395
401
|
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
402
|
+
sequential=sequential,
|
|
396
403
|
auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter,
|
|
397
404
|
auto_local=auto_local)
|
|
398
405
|
|
|
399
406
|
for key, value in data.items():
|
|
400
|
-
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
|
|
407
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling', 'sequential',
|
|
401
408
|
'auto_parameter', 'auto_local'):
|
|
402
409
|
continue # Already read
|
|
403
410
|
|
|
@@ -455,6 +462,7 @@ class Assembly:
|
|
|
455
462
|
json_node["name"] = node.name # 1
|
|
456
463
|
json_node["mt"] = node.mt # 2
|
|
457
464
|
json_node["profiling"] = node.profiling # 3
|
|
465
|
+
json_node["sequential"] = node.sequential # 3b
|
|
458
466
|
json_node["auto_link"] = node.auto_link # 4
|
|
459
467
|
json_node["auto_input"] = node.auto_input # 5
|
|
460
468
|
json_node["auto_output"] = node.auto_output # 6
|
|
@@ -510,8 +518,9 @@ class Assembly:
|
|
|
510
518
|
self.root = sdd.parse(self.description_pathname)
|
|
511
519
|
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
512
520
|
|
|
513
|
-
def make_fmu(self, dump_json=False):
|
|
514
|
-
self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname
|
|
521
|
+
def make_fmu(self, dump_json=False, fmi_version=2):
|
|
522
|
+
self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname,
|
|
523
|
+
fmi_version=fmi_version)
|
|
515
524
|
if dump_json:
|
|
516
525
|
dump_file = Path(self.input_pathname.stem + "-dump").with_suffix(".json")
|
|
517
526
|
logger.info(f"Dump Json '{dump_file}'")
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import importlib.util
|
|
2
2
|
import inspect
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import xmlschema
|
|
5
6
|
from xmlschema.validators.exceptions import XMLSchemaValidationError
|
|
6
7
|
from .operations import OperationAbstract
|
|
7
8
|
|
|
9
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class OperationGenericCheck(OperationAbstract):
|
|
10
13
|
SUPPORTED_FMI_VERSIONS = ('2.0', '3.0')
|
|
@@ -17,7 +20,7 @@ class OperationGenericCheck(OperationAbstract):
|
|
|
17
20
|
|
|
18
21
|
def fmi_attrs(self, attrs):
|
|
19
22
|
if attrs['fmiVersion'] not in self.SUPPORTED_FMI_VERSIONS:
|
|
20
|
-
|
|
23
|
+
logger.error(f"Expected FMI {','.join(self.SUPPORTED_FMI_VERSIONS)} versions.")
|
|
21
24
|
return
|
|
22
25
|
|
|
23
26
|
fmi_name = f"fmi{attrs['fmiVersion'][0]}"
|
|
@@ -27,15 +30,15 @@ class OperationGenericCheck(OperationAbstract):
|
|
|
27
30
|
try:
|
|
28
31
|
xmlschema.validate(self.fmu.descriptor_filename, schema=xsd_filename)
|
|
29
32
|
except XMLSchemaValidationError as error:
|
|
30
|
-
|
|
33
|
+
logger.error(error.reason, error.msg)
|
|
31
34
|
else:
|
|
32
35
|
self.compliant_with_version = attrs['fmiVersion']
|
|
33
36
|
|
|
34
37
|
def closure(self):
|
|
35
38
|
if self.compliant_with_version:
|
|
36
|
-
|
|
39
|
+
logger.info(f"This FMU seems to be compliant with FMI-{self.compliant_with_version}.")
|
|
37
40
|
else:
|
|
38
|
-
|
|
41
|
+
logger.error(f"This FMU does not validate with FMI standard.")
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
checker_list = [OperationGenericCheck]
|
|
@@ -44,20 +47,20 @@ checker_list = [OperationGenericCheck]
|
|
|
44
47
|
def add_from_file(checker_filename: str):
|
|
45
48
|
spec = importlib.util.spec_from_file_location(checker_filename, checker_filename)
|
|
46
49
|
if not spec:
|
|
47
|
-
|
|
50
|
+
logger.error(f"Cannot load '{checker_filename}'. Is this a python file?")
|
|
48
51
|
return
|
|
49
52
|
try:
|
|
50
53
|
checker_module = importlib.util.module_from_spec(spec)
|
|
51
54
|
try:
|
|
52
55
|
spec.loader.exec_module(checker_module)
|
|
53
56
|
except (ModuleNotFoundError, SyntaxError) as error:
|
|
54
|
-
|
|
57
|
+
logger.error(f"Cannot load '{checker_filename}': {error})")
|
|
55
58
|
return
|
|
56
59
|
|
|
57
60
|
for checker_name, checker_class in inspect.getmembers(checker_module, inspect.isclass):
|
|
58
61
|
if OperationAbstract in checker_class.__bases__:
|
|
59
62
|
checker_list.append(checker_class)
|
|
60
|
-
|
|
63
|
+
logger.info(f"Adding checker: {checker_filename}|{checker_name}")
|
|
61
64
|
|
|
62
65
|
except AttributeError:
|
|
63
|
-
|
|
66
|
+
logger.error(f"'{checker_filename}' should implement class 'OperationCheck'")
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from typing import *
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .utils import setup_logger, make_wide
|
|
9
|
+
from ..assembly import Assembly, AssemblyError
|
|
10
|
+
from ..container import FMUContainerError
|
|
11
|
+
from ..version import __version__ as version
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def fmucontainer(command_options: Sequence[str]):
|
|
15
|
+
logger = setup_logger()
|
|
16
|
+
|
|
17
|
+
logger.info(f"FMUContainer version {version}")
|
|
18
|
+
parser = argparse.ArgumentParser(prog="fmucontainer", description="Generate FMU from FMU's",
|
|
19
|
+
formatter_class=make_wide(argparse.ArgumentDefaultsHelpFormatter),
|
|
20
|
+
add_help=False,
|
|
21
|
+
epilog="see: https://github.com/grouperenault/fmu_manipulation_toolbox/blob/main/"
|
|
22
|
+
"container/README.md")
|
|
23
|
+
|
|
24
|
+
parser.add_argument('-h', '-help', action="help")
|
|
25
|
+
|
|
26
|
+
parser.add_argument("-fmu-directory", action="store", dest="fmu_directory", required=False, default=".",
|
|
27
|
+
help="Directory containing initial FMU’s and used to generate containers. "
|
|
28
|
+
"If not defined, current directory is used.")
|
|
29
|
+
|
|
30
|
+
parser.add_argument("-fmi", action="store", dest="fmi_version", required=False, default="2",
|
|
31
|
+
help="Define version of FMI to be used for container interface."
|
|
32
|
+
"Only '2' or '3' is supported.")
|
|
33
|
+
|
|
34
|
+
parser.add_argument("-container", action="append", dest="container_descriptions_list", default=[],
|
|
35
|
+
metavar="filename.{csv|json|ssp},[:step_size]", required=True,
|
|
36
|
+
help="Description of the container to create.")
|
|
37
|
+
|
|
38
|
+
parser.add_argument("-debug", action="store_true", dest="debug",
|
|
39
|
+
help="Add lot of useful log during the process.")
|
|
40
|
+
|
|
41
|
+
parser.add_argument("-no-auto-input", action="store_false", dest="auto_input", default=True,
|
|
42
|
+
help="Create ONLY explicit input.")
|
|
43
|
+
|
|
44
|
+
parser.add_argument("-no-auto-output", action="store_false", dest="auto_output", default=True,
|
|
45
|
+
help="Create ONLY explicit output.")
|
|
46
|
+
|
|
47
|
+
parser.add_argument("-auto-parameter", action="store_true", dest="auto_parameter", default=False,
|
|
48
|
+
help="Expose parameters of the embedded fmu's.")
|
|
49
|
+
|
|
50
|
+
parser.add_argument("-auto-local", action="store_true", dest="auto_local", default=False,
|
|
51
|
+
help="Expose local variables of the embedded fmu's.")
|
|
52
|
+
|
|
53
|
+
parser.add_argument("-no-auto-link", action="store_false", dest="auto_link", default=True,
|
|
54
|
+
help="Create ONLY explicit links.")
|
|
55
|
+
|
|
56
|
+
parser.add_argument("-mt", action="store_true", dest="mt", default=False,
|
|
57
|
+
help="Enable Multi-Threaded mode for the generated container.")
|
|
58
|
+
|
|
59
|
+
parser.add_argument("-profile", action="store_true", dest="profiling", default=False,
|
|
60
|
+
help="Enable Profiling mode for the generated container.")
|
|
61
|
+
|
|
62
|
+
parser.add_argument("-sequential", action="store_true", dest="sequential", default=False,
|
|
63
|
+
help="Use sequential mode to schedule embedded fmu's.")
|
|
64
|
+
|
|
65
|
+
parser.add_argument("-dump-json", action="store_true", dest="dump", default=False,
|
|
66
|
+
help="Dump a JSON file for each container.")
|
|
67
|
+
|
|
68
|
+
config = parser.parse_args(command_options)
|
|
69
|
+
|
|
70
|
+
if config.debug:
|
|
71
|
+
logger.setLevel(logging.DEBUG)
|
|
72
|
+
|
|
73
|
+
fmu_directory = Path(config.fmu_directory)
|
|
74
|
+
logger.info(f"FMU directory: '{fmu_directory}'")
|
|
75
|
+
|
|
76
|
+
for description in config.container_descriptions_list:
|
|
77
|
+
try:
|
|
78
|
+
tokens = description.split(":")
|
|
79
|
+
filename = ":".join(tokens[:-1])
|
|
80
|
+
step_size = float(tokens[-1])
|
|
81
|
+
except ValueError:
|
|
82
|
+
step_size = None
|
|
83
|
+
filename = description
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
assembly = Assembly(filename, step_size=step_size, auto_link=config.auto_link,
|
|
87
|
+
auto_input=config.auto_input, auto_output=config.auto_output,
|
|
88
|
+
auto_local=config.auto_local, mt=config.mt, sequential=config.sequential,
|
|
89
|
+
profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug,
|
|
90
|
+
auto_parameter=config.auto_parameter)
|
|
91
|
+
except FileNotFoundError as e:
|
|
92
|
+
logger.fatal(f"Cannot read file: {e}")
|
|
93
|
+
sys.exit(-1)
|
|
94
|
+
except (FMUContainerError, AssemblyError) as e:
|
|
95
|
+
logger.fatal(f"{filename}: {e}")
|
|
96
|
+
sys.exit(-2)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
assembly.make_fmu(dump_json=config.dump, fmi_version=int(config.fmi_version))
|
|
100
|
+
except FMUContainerError as e:
|
|
101
|
+
logger.fatal(f"{filename}: {e}")
|
|
102
|
+
sys.exit(-3)
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
fmucontainer(sys.argv[1:])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from typing import *
|
|
6
|
+
|
|
7
|
+
from .utils import setup_logger, make_wide
|
|
8
|
+
from ..split import FMUSplitter, FMUSplitterError
|
|
9
|
+
from ..version import __version__ as version
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def fmusplit(command_options: Sequence[str]):
|
|
13
|
+
logger = setup_logger()
|
|
14
|
+
|
|
15
|
+
logger.info(f"FMUSplit version {version}")
|
|
16
|
+
parser = argparse.ArgumentParser(prog="fmusplit", description="Split FMU Container into FMU's",
|
|
17
|
+
formatter_class=make_wide(argparse.ArgumentDefaultsHelpFormatter),
|
|
18
|
+
add_help=False,
|
|
19
|
+
epilog="see: https://github.com/grouperenault/fmu_manipulation_toolbox/blob/main/"
|
|
20
|
+
"container/README.md")
|
|
21
|
+
parser.add_argument('-h', '-help', action="help")
|
|
22
|
+
|
|
23
|
+
parser.add_argument("-debug", action="store_true", dest="debug",
|
|
24
|
+
help="Add lot of useful log during the process.")
|
|
25
|
+
|
|
26
|
+
parser.add_argument("-fmu", action="append", dest="fmu_filename_list", default=[],
|
|
27
|
+
metavar="filename.fmu", required=True,
|
|
28
|
+
help="Description of the FMU container to split.")
|
|
29
|
+
|
|
30
|
+
config = parser.parse_args(command_options)
|
|
31
|
+
|
|
32
|
+
if config.debug:
|
|
33
|
+
logger.setLevel(logging.DEBUG)
|
|
34
|
+
|
|
35
|
+
for fmu_filename in config.fmu_filename_list:
|
|
36
|
+
try:
|
|
37
|
+
splitter = FMUSplitter(fmu_filename)
|
|
38
|
+
splitter.split_fmu()
|
|
39
|
+
except FMUSplitterError as e:
|
|
40
|
+
logger.fatal(f"{fmu_filename}: {e}")
|
|
41
|
+
sys.exit(-1)
|
|
42
|
+
except FileNotFoundError as e:
|
|
43
|
+
logger.fatal(f"Cannot read file: {e}")
|
|
44
|
+
sys.exit(-2)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
fmusplit(sys.argv[1:])
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from typing import *
|
|
5
|
+
|
|
6
|
+
from .utils import setup_logger, make_wide
|
|
7
|
+
from ..operations import (OperationSummary, OperationError, OperationRemoveRegexp,
|
|
8
|
+
OperationRemoveSources, OperationTrimUntil, OperationKeepOnlyRegexp, OperationMergeTopLevel,
|
|
9
|
+
OperationStripTopLevel, OperationRenameFromCSV, OperationSaveNamesToCSV, FMU, FMUError)
|
|
10
|
+
from ..remoting import (OperationAddFrontendWin32, OperationAddFrontendWin64, OperationAddRemotingWin32,
|
|
11
|
+
OperationAddRemotingWin64)
|
|
12
|
+
from ..checker import checker_list
|
|
13
|
+
from ..version import __version__ as version
|
|
14
|
+
from ..help import Help
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fmutool(command_options: Sequence[str]):
|
|
18
|
+
logger = setup_logger()
|
|
19
|
+
|
|
20
|
+
logger.info(f"FMU Manipulation Toolbox version {version}")
|
|
21
|
+
help_message = Help()
|
|
22
|
+
|
|
23
|
+
parser = argparse.ArgumentParser(prog='fmutool',
|
|
24
|
+
description="Analyse and Manipulate a FMU by modifying its 'modelDescription.xml'",
|
|
25
|
+
formatter_class=make_wide(argparse.HelpFormatter),
|
|
26
|
+
add_help=False,
|
|
27
|
+
epilog="see: https://github.com/grouperenault/fmu_manipulation_toolbox/blob/main/README.md")
|
|
28
|
+
|
|
29
|
+
def add_option(option, *args, **kwargs):
|
|
30
|
+
parser.add_argument(option, *args, help=help_message.usage(option), **kwargs)
|
|
31
|
+
|
|
32
|
+
add_option('-h', '-help', action="help")
|
|
33
|
+
|
|
34
|
+
# I/O
|
|
35
|
+
add_option('-input', action='store', dest='fmu_input', default=None, required=True, metavar='path/to/module.fmu')
|
|
36
|
+
add_option('-output', action='store', dest='fmu_output', default=None, metavar='path/to/module-modified.fmu')
|
|
37
|
+
|
|
38
|
+
# Port name manipulation
|
|
39
|
+
add_option('-remove-toplevel', action='append_const', dest='operations_list', const=OperationStripTopLevel())
|
|
40
|
+
add_option('-merge-toplevel', action='append_const', dest='operations_list', const=OperationMergeTopLevel())
|
|
41
|
+
add_option('-trim-until', action='append', dest='operations_list', type=OperationTrimUntil, metavar='prefix')
|
|
42
|
+
add_option('-remove-regexp', action='append', dest='operations_list', type=OperationRemoveRegexp,
|
|
43
|
+
metavar='regular-expression')
|
|
44
|
+
add_option('-keep-only-regexp', action='append', dest='operations_list', type=OperationKeepOnlyRegexp,
|
|
45
|
+
metavar='regular-expression')
|
|
46
|
+
add_option('-remove-all', action='append_const', dest='operations_list', const=OperationRemoveRegexp('.*'))
|
|
47
|
+
|
|
48
|
+
# Batch Rename
|
|
49
|
+
add_option('-dump-csv', action='append', dest='operations_list', type=OperationSaveNamesToCSV,
|
|
50
|
+
metavar='path/to/list.csv')
|
|
51
|
+
add_option('-rename-from-csv', action='append', dest='operations_list', type=OperationRenameFromCSV,
|
|
52
|
+
metavar='path/to/translation.csv')
|
|
53
|
+
|
|
54
|
+
# Remoting
|
|
55
|
+
add_option('-add-remoting-win32', action='append_const', dest='operations_list', const=OperationAddRemotingWin32())
|
|
56
|
+
add_option('-add-remoting-win64', action='append_const', dest='operations_list', const=OperationAddRemotingWin64())
|
|
57
|
+
add_option('-add-frontend-win32', action='append_const', dest='operations_list', const=OperationAddFrontendWin32())
|
|
58
|
+
add_option('-add-frontend-win64', action='append_const', dest='operations_list', const=OperationAddFrontendWin64())
|
|
59
|
+
|
|
60
|
+
# Extraction / Removal
|
|
61
|
+
add_option('-extract-descriptor', action='store', dest='extract_description',
|
|
62
|
+
metavar='path/to/saved-modelDescriptor.xml')
|
|
63
|
+
add_option('-remove-sources', action='append_const', dest='operations_list',
|
|
64
|
+
const=OperationRemoveSources())
|
|
65
|
+
# Filter
|
|
66
|
+
add_option('-only-parameters', action='append_const', dest='apply_on', const='parameter')
|
|
67
|
+
add_option('-only-inputs', action='append_const', dest='apply_on', const='input')
|
|
68
|
+
add_option('-only-outputs', action='append_const', dest='apply_on', const='output')
|
|
69
|
+
add_option('-only-locals', action='append_const', dest='apply_on', const='local')
|
|
70
|
+
# Checker
|
|
71
|
+
add_option('-summary', action='append_const', dest='operations_list', const=OperationSummary())
|
|
72
|
+
add_option('-check', action='append_const', dest='operations_list', const=[checker() for checker in checker_list])
|
|
73
|
+
|
|
74
|
+
cli_options = parser.parse_args(command_options)
|
|
75
|
+
# handle the "no operation" use case
|
|
76
|
+
if not cli_options.operations_list:
|
|
77
|
+
cli_options.operations_list = []
|
|
78
|
+
|
|
79
|
+
if cli_options.fmu_input == cli_options.fmu_output:
|
|
80
|
+
logger.fatal(f"'-input' and '-output' should point to different files.")
|
|
81
|
+
sys.exit(-3)
|
|
82
|
+
|
|
83
|
+
logger.info(f"READING Input='{cli_options.fmu_input}'")
|
|
84
|
+
try:
|
|
85
|
+
fmu = FMU(cli_options.fmu_input)
|
|
86
|
+
except FMUError as reason:
|
|
87
|
+
logger.fatal(f"{reason}")
|
|
88
|
+
sys.exit(-4)
|
|
89
|
+
|
|
90
|
+
if cli_options.apply_on:
|
|
91
|
+
logger.info("Applying operation for :")
|
|
92
|
+
for causality in cli_options.apply_on:
|
|
93
|
+
logger.info(f" - causality = {causality}")
|
|
94
|
+
|
|
95
|
+
# Checker operations are added as a list into operations_list
|
|
96
|
+
def operation_iterator():
|
|
97
|
+
for op in cli_options.operations_list:
|
|
98
|
+
if isinstance(op, list):
|
|
99
|
+
for sub_op in op:
|
|
100
|
+
yield sub_op
|
|
101
|
+
else:
|
|
102
|
+
yield op
|
|
103
|
+
|
|
104
|
+
for operation in operation_iterator():
|
|
105
|
+
logger.info(f" => {operation}")
|
|
106
|
+
try:
|
|
107
|
+
fmu.apply_operation(operation, cli_options.apply_on)
|
|
108
|
+
except OperationError as reason:
|
|
109
|
+
logger.fatal(f"{reason}")
|
|
110
|
+
sys.exit(-6)
|
|
111
|
+
|
|
112
|
+
if cli_options.extract_description:
|
|
113
|
+
logger.info(f"WRITING ModelDescriptor='{cli_options.extract_description}'")
|
|
114
|
+
fmu.save_descriptor(cli_options.extract_description)
|
|
115
|
+
|
|
116
|
+
if cli_options.fmu_output:
|
|
117
|
+
logger.info(f"WRITING Output='{cli_options.fmu_output}'")
|
|
118
|
+
try:
|
|
119
|
+
fmu.repack(cli_options.fmu_output)
|
|
120
|
+
except FMUError as reason:
|
|
121
|
+
logger.fatal(f"FATAL ERROR: {reason}")
|
|
122
|
+
sys.exit(-5)
|
|
123
|
+
else:
|
|
124
|
+
logger.info(f"INFO Modified FMU is not saved. If necessary use '-output' option.")
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
fmutool(sys.argv[1:])
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from colorama import Fore, Style, init
|
|
4
|
+
|
|
5
|
+
def setup_logger():
|
|
6
|
+
class CustomFormatter(logging.Formatter):
|
|
7
|
+
def format(self, record):
|
|
8
|
+
log_format = "%(levelname)-8s | %(message)s"
|
|
9
|
+
format_per_level = {
|
|
10
|
+
logging.DEBUG: str(Fore.BLUE) + log_format,
|
|
11
|
+
logging.INFO: str(Fore.CYAN) + log_format,
|
|
12
|
+
logging.WARNING: str(Fore.YELLOW) + log_format,
|
|
13
|
+
logging.ERROR: str(Fore.RED) + log_format,
|
|
14
|
+
logging.CRITICAL: str(Fore.RED + Style.BRIGHT) + log_format,
|
|
15
|
+
}
|
|
16
|
+
formatter = logging.Formatter(format_per_level[record.levelno])
|
|
17
|
+
return formatter.format(record)
|
|
18
|
+
init()
|
|
19
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
20
|
+
handler = logging.StreamHandler(stream=sys.stdout)
|
|
21
|
+
handler.setFormatter(CustomFormatter())
|
|
22
|
+
logger.addHandler(handler)
|
|
23
|
+
logger.setLevel(logging.INFO)
|
|
24
|
+
|
|
25
|
+
return logger
|
|
26
|
+
|
|
27
|
+
def make_wide(formatter, w=120, h=36):
|
|
28
|
+
"""Return a wider HelpFormatter, if possible."""
|
|
29
|
+
try:
|
|
30
|
+
# https://stackoverflow.com/a/5464440
|
|
31
|
+
# beware: "Only the name of this class is considered a public API."
|
|
32
|
+
kwargs = {'width': w, 'max_help_position': h}
|
|
33
|
+
formatter(None, **kwargs)
|
|
34
|
+
return lambda prog: formatter(prog, **kwargs)
|
|
35
|
+
except TypeError:
|
|
36
|
+
return formatter
|