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.
Files changed (34) hide show
  1. fmu_manipulation_toolbox/__init__.py +0 -1
  2. fmu_manipulation_toolbox/__main__.py +1 -1
  3. fmu_manipulation_toolbox/__version__.py +1 -1
  4. fmu_manipulation_toolbox/assembly.py +21 -12
  5. fmu_manipulation_toolbox/checker.py +11 -8
  6. fmu_manipulation_toolbox/cli/__init__.py +0 -0
  7. fmu_manipulation_toolbox/cli/fmucontainer.py +105 -0
  8. fmu_manipulation_toolbox/cli/fmusplit.py +48 -0
  9. fmu_manipulation_toolbox/cli/fmutool.py +127 -0
  10. fmu_manipulation_toolbox/cli/utils.py +36 -0
  11. fmu_manipulation_toolbox/container.py +534 -247
  12. fmu_manipulation_toolbox/gui.py +47 -55
  13. fmu_manipulation_toolbox/gui_style.py +8 -0
  14. fmu_manipulation_toolbox/help.py +3 -0
  15. fmu_manipulation_toolbox/operations.py +251 -179
  16. fmu_manipulation_toolbox/remoting.py +107 -0
  17. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  18. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  19. fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
  20. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  21. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  22. fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
  23. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  24. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  25. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  26. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  27. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  28. fmu_manipulation_toolbox/split.py +67 -34
  29. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
  30. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/RECORD +34 -27
  31. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
  32. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/entry_points.txt +0 -0
  33. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
  34. {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
-
@@ -10,7 +10,7 @@ def gui():
10
10
 
11
11
 
12
12
  def cli():
13
- from .cli import fmutool
13
+ from .cli.fmutool import fmutool
14
14
  fmutool()
15
15
 
16
16
 
@@ -1 +1 @@
1
- 'V1.8.4.3-beta'
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, debug=debug)
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
- auto_input=self.default_auto_input, auto_output=self.default_auto_output,
287
- auto_parameter=self.default_auto_parameter, auto_local=self.default_auto_local)
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
- print(f"ERROR: Expected FMI {','.join(self.SUPPORTED_FMI_VERSIONS)} versions.")
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
- print(error.reason, error.msg)
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
- print(f"INFO: This FMU seems to be compliant with FMI-{self.compliant_with_version}.")
39
+ logger.info(f"This FMU seems to be compliant with FMI-{self.compliant_with_version}.")
37
40
  else:
38
- print(f"ERROR: This FMU does not validate with FMI standard.")
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
- print(f"ERROR: Cannot load {checker_filename}. Is this a python file?")
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
- print(f"ERROR: Cannot load {checker_filename}: {error})")
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
- print(f"Adding checker: {checker_filename}|{checker_name}")
63
+ logger.info(f"Adding checker: {checker_filename}|{checker_name}")
61
64
 
62
65
  except AttributeError:
63
- print(f"ERROR: {checker_filename} should implement class 'OperationCheck'")
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