mps-cli 0.6.0__tar.gz → 0.6.1__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.
- {mps_cli-0.6.0 → mps_cli-0.6.1}/PKG-INFO +3 -1
- {mps_cli-0.6.0 → mps_cli-0.6.1}/pyproject.toml +6 -1
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/demo.py +3 -0
- mps_cli-0.6.1/src/mpscli/model/SConcept.py +26 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SLanguage.py +5 -5
- mps_cli-0.6.1/src/mpscli/model/builder/SLanguageBuilder.py +128 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/SModelBuilderBase.py +18 -6
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/SModelBuilderBinaryPersistency.py +50 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/SSolutionBuilder.py +14 -19
- mps_cli-0.6.1/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +311 -0
- mps_cli-0.6.1/src/mpscli/model/builder/utils/DiskModelLoader.py +377 -0
- mps_cli-0.6.1/src/mpscli/model/builder/utils/JarModelLoader.py +328 -0
- mps_cli-0.6.1/src/mpscli/model/builder/utils/JarScanner.py +167 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/utils/ModelCache.py +12 -3
- mps_cli-0.6.1/src/mpscli/model/builder/utils/MpbBatchParser.py +207 -0
- mps_cli-0.6.1/src/mpscli/model/builder/utils/ParseCache.py +255 -0
- mps_cli-0.6.1/tests/binary/__init__.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_format_parity.py +2 -1
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_language_from_mpl.py +2 -1
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_library_top.py +2 -1
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_references.py +2 -1
- mps_cli-0.6.1/tests/binary/test_binary_repository.py +93 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_repository_completeness.py +4 -3
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_base.py +2 -2
- mps_cli-0.6.1/tests/test_disk_model_loader.py +249 -0
- mps_cli-0.6.1/tests/test_jar_model_loader.py +181 -0
- mps_cli-0.6.1/tests/test_jar_scanner.py +147 -0
- mps_cli-0.6.1/tests/test_model_cache.py +130 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_modules_and_models.py +12 -6
- mps_cli-0.6.1/tests/test_mpb_batch_parser.py +145 -0
- mps_cli-0.6.1/tests/test_parse_cache.py +291 -0
- mps_cli-0.6.1/tests/test_solutions_repository_builder.py +166 -0
- mps_cli-0.6.0/Readme.md +0 -42
- mps_cli-0.6.0/src/mpscli/model/SConcept.py +0 -21
- mps_cli-0.6.0/src/mpscli/model/builder/SLanguageBuilder.py +0 -105
- mps_cli-0.6.0/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +0 -126
- mps_cli-0.6.0/src/mpscli/model/builder/utils/MpbBatchParser.py +0 -124
- mps_cli-0.6.0/tests/binary/test_binary_repository.py +0 -52
- {mps_cli-0.6.0 → mps_cli-0.6.1}/.gitignore +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/LICENSE +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/README.md +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/__init__.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/demo_language_extraction.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SModel.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SNode.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SNodeRef.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SRepository.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/SSolution.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/__init__.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/SModelBuilderDefaultPersistency.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/SModelBuilderFilePerRootPersistency.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/__init__.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/constants.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/imports.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/model_input_stream.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/module_refs.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/nodes.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/registry.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/used_languages.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/utils.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/binary/uuid_utils.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/utils/JarUtils.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/src/mpscli/model/builder/utils/NodeIdEncodingUtils.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/__init__.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/Readme.md +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_cross_module_ref.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_header.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_imports.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_model_imports.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_node_tree.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_performance.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_persistency_low_level_access.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/binary/test_binary_registry.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/context.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_language_extraction.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_model_attributes.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_model_imports.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_node_id_encoding_utils.py +0 -0
- {mps_cli-0.6.0 → mps_cli-0.6.1}/tests/test_nodes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mps-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: MPS Command Line Interface (MPS-CLI) is a library to parse MPS files and build the model as Python objects.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mbeddr/mps-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/mbeddr/mps-cli/issues
|
|
@@ -10,6 +10,8 @@ Classifier: License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.7
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: parameterized; extra == 'dev'
|
|
13
15
|
Description-Content-Type: text/markdown
|
|
14
16
|
|
|
15
17
|
## MPS CLI Python Library
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mps-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Daniel Ratiu", email="ratiud@googlemail.com" },
|
|
10
10
|
{ name="Prithvi Venkat", email="prithvi.rvce@gmail.com" },
|
|
@@ -19,6 +19,11 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = [
|
|
24
|
+
"parameterized",
|
|
25
|
+
]
|
|
26
|
+
|
|
22
27
|
[project.urls]
|
|
23
28
|
"Homepage" = "https://github.com/mbeddr/mps-cli"
|
|
24
29
|
"Bug Tracker" = "https://github.com/mbeddr/mps-cli/issues"
|
|
@@ -13,6 +13,7 @@ import sys
|
|
|
13
13
|
import time
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from pathlib import Path
|
|
16
|
+
import logging
|
|
16
17
|
|
|
17
18
|
sys.path.insert(1, "..")
|
|
18
19
|
|
|
@@ -283,6 +284,7 @@ def main() -> None:
|
|
|
283
284
|
for c in lan.concepts:
|
|
284
285
|
c.print_concept_details()
|
|
285
286
|
|
|
287
|
+
|
|
286
288
|
if __name__ == "__main__":
|
|
287
289
|
import multiprocessing
|
|
288
290
|
|
|
@@ -291,6 +293,7 @@ if __name__ == "__main__":
|
|
|
291
293
|
# redirect stdout to a log file
|
|
292
294
|
log_file = f"mps_debug_{datetime.now().strftime('%H%M%S')}.log"
|
|
293
295
|
sys.stdout = open(log_file, "w", encoding="utf-8", buffering=1)
|
|
296
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stderr, format="%(message)s")
|
|
294
297
|
print(f"Logging to: {log_file}")
|
|
295
298
|
|
|
296
299
|
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
_log = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SConcept:
|
|
7
|
+
|
|
8
|
+
def __init__(self, name, uuid):
|
|
9
|
+
self.name = name
|
|
10
|
+
self.uuid = uuid
|
|
11
|
+
self.properties = []
|
|
12
|
+
self.children = []
|
|
13
|
+
self.references = []
|
|
14
|
+
|
|
15
|
+
def print_concept_details(self):
|
|
16
|
+
_log.debug("concept: %s", self.name)
|
|
17
|
+
_log.debug("\tproperties: ")
|
|
18
|
+
for property in self.properties:
|
|
19
|
+
_log.debug("\t\t%s", property)
|
|
20
|
+
_log.debug("\tchildren: ")
|
|
21
|
+
for child in self.children:
|
|
22
|
+
_log.debug("\t\t%s", child)
|
|
23
|
+
_log.debug("\treferences: ")
|
|
24
|
+
for reference in self.references:
|
|
25
|
+
_log.debug("\t\t%s", reference)
|
|
26
|
+
_log.debug("<<<")
|
|
@@ -5,7 +5,10 @@ class SLanguage:
|
|
|
5
5
|
self.uuid = uuid
|
|
6
6
|
self.concepts = []
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# kept in sync with concepts list by SLanguageBuilder.get_concept()..
|
|
9
|
+
self._concepts_by_name = {}
|
|
10
|
+
|
|
11
|
+
# version number from the languageVersion attribute in the .mpl file.
|
|
9
12
|
# stays 0 if this language was only seen via registry (never had its .mpl read)
|
|
10
13
|
self.language_version = 0
|
|
11
14
|
|
|
@@ -15,10 +18,7 @@ class SLanguage:
|
|
|
15
18
|
self.models = []
|
|
16
19
|
|
|
17
20
|
def find_concept_by_name(self, name):
|
|
18
|
-
|
|
19
|
-
if c.name == name:
|
|
20
|
-
return c
|
|
21
|
-
return None
|
|
21
|
+
return self._concepts_by_name.get(name)
|
|
22
22
|
|
|
23
23
|
def find_model_by_name(self, suffix):
|
|
24
24
|
# find a specific aspect model by the last segment of its name.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import xml.etree.ElementTree as ET
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Lock
|
|
5
|
+
|
|
6
|
+
from mpscli.model.SLanguage import SLanguage
|
|
7
|
+
from mpscli.model.SConcept import SConcept
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SLanguageBuilder:
|
|
11
|
+
languages = {}
|
|
12
|
+
# protectss languages dict, per-language _concepts_by_name dicts and per-concept properties/children/references
|
|
13
|
+
# lists also.. needed because all get_ methods are called from multiple threads during parallel
|
|
14
|
+
# parsing (phase3 ProcessPool workers and phase4 ProcessPool workerss)
|
|
15
|
+
_lock = Lock()
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def get_language(cls, name, uuid):
|
|
19
|
+
lan = cls.languages.get(name)
|
|
20
|
+
if lan is not None:
|
|
21
|
+
return lan
|
|
22
|
+
with cls._lock:
|
|
23
|
+
lan = cls.languages.get(name)
|
|
24
|
+
if lan is None:
|
|
25
|
+
lan = SLanguage(name, uuid)
|
|
26
|
+
cls.languages[name] = lan
|
|
27
|
+
return lan
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_concept(cls, language, concept_name, concept_uuid):
|
|
31
|
+
# always acquire lock since _concepts_by_name is mutated here and by other threads..
|
|
32
|
+
# SLanguage.__init__ always initialises _concepts_by_name so no hasattr check needed
|
|
33
|
+
with cls._lock:
|
|
34
|
+
concept = language._concepts_by_name.get(concept_name)
|
|
35
|
+
if concept is None:
|
|
36
|
+
concept = SConcept(concept_name, concept_uuid)
|
|
37
|
+
language.concepts.append(concept)
|
|
38
|
+
language._concepts_by_name[concept_name] = concept
|
|
39
|
+
return concept
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def get_property(cls, concept, property_name):
|
|
43
|
+
# fast path cuz list iteration is GIL-atomic for reads in CPython
|
|
44
|
+
node_property = next(
|
|
45
|
+
(p for p in concept.properties if p == property_name), None
|
|
46
|
+
)
|
|
47
|
+
if node_property is not None:
|
|
48
|
+
return node_property
|
|
49
|
+
with cls._lock:
|
|
50
|
+
# double-check inside lock since another thread may have added it
|
|
51
|
+
node_property = next(
|
|
52
|
+
(p for p in concept.properties if p == property_name), None
|
|
53
|
+
)
|
|
54
|
+
if node_property is None:
|
|
55
|
+
concept.properties.append(property_name)
|
|
56
|
+
node_property = property_name
|
|
57
|
+
return node_property
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_child(cls, concept, child_name):
|
|
61
|
+
child_role = next((c for c in concept.children if c == child_name), None)
|
|
62
|
+
if child_role is not None:
|
|
63
|
+
return child_role
|
|
64
|
+
with cls._lock:
|
|
65
|
+
child_role = next((c for c in concept.children if c == child_name), None)
|
|
66
|
+
if child_role is None:
|
|
67
|
+
concept.children.append(child_name)
|
|
68
|
+
child_role = child_name
|
|
69
|
+
return child_role
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def get_reference(cls, concept, reference_name):
|
|
73
|
+
reference_role = next(
|
|
74
|
+
(r for r in concept.references if r == reference_name), None
|
|
75
|
+
)
|
|
76
|
+
if reference_role is not None:
|
|
77
|
+
return reference_role
|
|
78
|
+
with cls._lock:
|
|
79
|
+
reference_role = next(
|
|
80
|
+
(r for r in concept.references if r == reference_name), None
|
|
81
|
+
)
|
|
82
|
+
if reference_role is None:
|
|
83
|
+
concept.references.append(reference_name)
|
|
84
|
+
reference_role = reference_name
|
|
85
|
+
return reference_role
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def load_from_mpl(cls, mpl_path: Path) -> SLanguage:
|
|
89
|
+
# reads a .mpl file and enriches the matching SLanguage with its version
|
|
90
|
+
# number and aspect models. if the language was already registered via
|
|
91
|
+
# a registry section in a .mpb file, get_language() returns the same object
|
|
92
|
+
# so no duplicate entries are created.
|
|
93
|
+
try:
|
|
94
|
+
return cls._read_and_enrich(mpl_path)
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
warnings.warn(f"Failed to read language from {mpl_path.name}: {exc}")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def _read_and_enrich(cls, mpl_path: Path) -> SLanguage:
|
|
101
|
+
root = ET.parse(mpl_path).getroot()
|
|
102
|
+
namespace = root.get("namespace", "")
|
|
103
|
+
uuid = root.get("uuid", "")
|
|
104
|
+
version = int(root.get("languageVersion", "0"))
|
|
105
|
+
lang = cls.get_language(namespace, uuid)
|
|
106
|
+
lang.language_version = version
|
|
107
|
+
lang.models = cls._load_aspect_models(mpl_path.parent / "models")
|
|
108
|
+
return lang
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def _load_aspect_models(cls, models_dir: Path) -> list:
|
|
112
|
+
# parse every .mpb in the models directory next to the .mpl file
|
|
113
|
+
# these are the language aspects which is structure,, behavior, editor, constraints, typesystem, etc..
|
|
114
|
+
if not models_dir.exists():
|
|
115
|
+
return []
|
|
116
|
+
from mpscli.model.builder.SModelBuilderBinaryPersistency import (
|
|
117
|
+
SModelBuilderBinaryPersistency,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
loaded = []
|
|
121
|
+
for mpb_file in sorted(models_dir.glob("*.mpb")):
|
|
122
|
+
try:
|
|
123
|
+
model = SModelBuilderBinaryPersistency().build(str(mpb_file))
|
|
124
|
+
if model is not None:
|
|
125
|
+
loaded.append(model)
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
warnings.warn(f"Failed to parse aspect model {mpb_file.name}: {exc}")
|
|
128
|
+
return loaded
|
|
@@ -37,7 +37,11 @@ class SModelBuilderBase:
|
|
|
37
37
|
else:
|
|
38
38
|
ref_model_index = ref_to[0 : ref_to.find(":")]
|
|
39
39
|
ref_node_uuid = ref_to[ref_to.find(":") + 1 : len(ref_to)]
|
|
40
|
-
s_node_ref = SNodeRef(
|
|
40
|
+
s_node_ref = SNodeRef(
|
|
41
|
+
self.index_2_imported_model_uuid[ref_model_index],
|
|
42
|
+
ref_node_uuid,
|
|
43
|
+
resolve_info,
|
|
44
|
+
)
|
|
41
45
|
ref_name = self.index_2_reference_role[ref_role]
|
|
42
46
|
s_node.references[ref_name] = s_node_ref
|
|
43
47
|
for child_node_xml in node_xml.findall("node"):
|
|
@@ -45,10 +49,12 @@ class SModelBuilderBase:
|
|
|
45
49
|
s_node.children.append(child_node)
|
|
46
50
|
|
|
47
51
|
return s_node
|
|
52
|
+
|
|
48
53
|
@staticmethod
|
|
49
54
|
def is_model_generatable(model_xml_node):
|
|
50
55
|
return any(
|
|
51
|
-
attribute.get("name") == "doNotGenerate"
|
|
56
|
+
attribute.get("name") == "doNotGenerate"
|
|
57
|
+
and attribute.get("value") == "true"
|
|
52
58
|
for attribute in model_xml_node.findall("attribute")
|
|
53
59
|
)
|
|
54
60
|
|
|
@@ -64,7 +70,7 @@ class SModelBuilderBase:
|
|
|
64
70
|
imported_model_ref = import_xml_node.get("ref")
|
|
65
71
|
imported_model_uuid = imported_model_ref[0 : imported_model_ref.find("(")]
|
|
66
72
|
imported_models[import_index] = imported_model_uuid
|
|
67
|
-
self.index_2_imported_model_uuid[import_index] = imported_model_uuid
|
|
73
|
+
self.index_2_imported_model_uuid[import_index] = imported_model_uuid
|
|
68
74
|
|
|
69
75
|
return imported_models
|
|
70
76
|
|
|
@@ -74,7 +80,9 @@ class SModelBuilderBase:
|
|
|
74
80
|
model_uuid = model_ref[0 : model_ref.find("(")]
|
|
75
81
|
model_is_do_not_generate = self.is_model_generatable(model_xml_node)
|
|
76
82
|
model_imported_models = self.extract_imported_models(self, model_xml_node)
|
|
77
|
-
model = SModel(
|
|
83
|
+
model = SModel(
|
|
84
|
+
model_name, model_uuid, model_is_do_not_generate, model_imported_models
|
|
85
|
+
)
|
|
78
86
|
return model
|
|
79
87
|
|
|
80
88
|
def extract_registry(self, model_xml_node):
|
|
@@ -86,13 +94,17 @@ class SModelBuilderBase:
|
|
|
86
94
|
for concept_xml_node in language_xml_node.findall("concept"):
|
|
87
95
|
concept_id = concept_xml_node.get("id")
|
|
88
96
|
concept_name = concept_xml_node.get("name")
|
|
89
|
-
concept = SLanguageBuilder.get_concept(
|
|
97
|
+
concept = SLanguageBuilder.get_concept(
|
|
98
|
+
language, concept_name, concept_id
|
|
99
|
+
)
|
|
90
100
|
concept_index = concept_xml_node.get("index")
|
|
91
101
|
self.index_2_concept[concept_index] = concept
|
|
92
102
|
for property_xml_node in concept_xml_node.findall("property"):
|
|
93
103
|
property_name = property_xml_node.get("name")
|
|
94
104
|
property_index = property_xml_node.get("index")
|
|
95
|
-
node_property = SLanguageBuilder.get_property(
|
|
105
|
+
node_property = SLanguageBuilder.get_property(
|
|
106
|
+
concept, property_name
|
|
107
|
+
)
|
|
96
108
|
self.index_2_property[property_index] = node_property
|
|
97
109
|
for child_xml_node in concept_xml_node.findall("child"):
|
|
98
110
|
child_name = child_xml_node.get("name")
|
|
@@ -135,6 +135,56 @@ class SModelBuilderBinaryPersistency(SModelBuilderBase):
|
|
|
135
135
|
|
|
136
136
|
return model
|
|
137
137
|
|
|
138
|
+
def build_from_bytes(self, data: bytes, path_hint: str = ""):
|
|
139
|
+
reader = ModelInputStream(data)
|
|
140
|
+
|
|
141
|
+
# 1. header: extracts stream version andd model UUID and model name
|
|
142
|
+
version, model_uuid, model_name = self._load_header(reader)
|
|
143
|
+
self.stream_version = version
|
|
144
|
+
|
|
145
|
+
uuid_str = model_uuid or "r:unknown"
|
|
146
|
+
name_str = model_name or "unknown.model"
|
|
147
|
+
|
|
148
|
+
# Import index 0 is always the current models own uuid..
|
|
149
|
+
# Java's SModel.importedModels() lists imports starting from index 1 and index 0 is implicitly
|
|
150
|
+
# the model itself used when resolving REF_THIS_MODEL
|
|
151
|
+
self.index_2_imported_model_uuid["0"] = uuid_str
|
|
152
|
+
model = SModel(name_str, uuid_str, False)
|
|
153
|
+
|
|
154
|
+
# 2. registry: builds concept/property/reference/child index maps
|
|
155
|
+
load_registry(reader, self)
|
|
156
|
+
|
|
157
|
+
# 3. model properties - used languages, devkits, imports
|
|
158
|
+
try:
|
|
159
|
+
self._load_model_properties(reader, version)
|
|
160
|
+
except _UnknownSubKind as e:
|
|
161
|
+
import warnings
|
|
162
|
+
|
|
163
|
+
warnings.warn(
|
|
164
|
+
f"[build_from_bytes] {path_hint}: {e} - skipped to MODEL_START"
|
|
165
|
+
)
|
|
166
|
+
advance_until_after(reader, MODEL_START)
|
|
167
|
+
return model
|
|
168
|
+
|
|
169
|
+
model.imported_models = {
|
|
170
|
+
index: imported_model_uuid
|
|
171
|
+
for index, imported_model_uuid in self.index_2_imported_model_uuid.items()
|
|
172
|
+
if index != "0"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# 4. model start..
|
|
176
|
+
token = reader.read_u32()
|
|
177
|
+
if token != MODEL_START:
|
|
178
|
+
raise RuntimeError(
|
|
179
|
+
f"Expected MODEL_START (0x{MODEL_START:08X}), "
|
|
180
|
+
f"got 0x{token:08X} at pos {reader.tell() - 4}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# 5. Node tree: recursive read_children populates model.root_nodes..
|
|
184
|
+
read_children(reader, self, model, None)
|
|
185
|
+
|
|
186
|
+
return model
|
|
187
|
+
|
|
138
188
|
def _load_header(self, reader: ModelInputStream):
|
|
139
189
|
# Mirrors BinaryPersistence.loadHeader() in:
|
|
140
190
|
# https://github.com/JetBrains/MPS/blob/6236c4073eac3cde78506add6b0fa90601d76009/core/persistence/source/jetbrains/mps/persistence/binary/BinaryPersistence.java
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
2
3
|
import xml.etree.ElementTree as ET
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import List, Optional, Tuple
|
|
5
6
|
|
|
7
|
+
_log = logging.getLogger(__name__)
|
|
8
|
+
|
|
6
9
|
from mpscli.model.SSolution import SSolution
|
|
7
|
-
from mpscli.model.builder.
|
|
8
|
-
SModelBuilderDefaultPersistency,
|
|
9
|
-
)
|
|
10
|
-
from mpscli.model.builder.SModelBuilderFilePerRootPersistency import (
|
|
11
|
-
SModelBuilderFilePerRootPersistency,
|
|
12
|
-
)
|
|
10
|
+
from mpscli.model.builder.utils.DiskModelLoader import parse_fpr, parse_mps
|
|
13
11
|
from mpscli.model.builder.utils.MpbBatchParser import MpbBatchParser
|
|
14
12
|
|
|
15
13
|
|
|
@@ -20,8 +18,9 @@ class SSolutionBuilder:
|
|
|
20
18
|
def build_solution(self, path_to_msd_file: Path) -> Optional[SSolution]:
|
|
21
19
|
models_dir = path_to_msd_file.parent / "models"
|
|
22
20
|
if not models_dir.exists():
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
_log.warning(
|
|
22
|
+
"'models' directory not found - no model loaded from: %s",
|
|
23
|
+
path_to_msd_file.parent,
|
|
25
24
|
)
|
|
26
25
|
return None
|
|
27
26
|
solutions = self.build_all([path_to_msd_file])
|
|
@@ -40,12 +39,10 @@ class SSolutionBuilder:
|
|
|
40
39
|
for msd_path in msd_paths:
|
|
41
40
|
solution = self._extract_solution_info(msd_path)
|
|
42
41
|
solution.path_to_solution_file = msd_path
|
|
43
|
-
|
|
44
42
|
models_dir = msd_path.parent / "models"
|
|
45
43
|
if not models_dir.exists():
|
|
46
44
|
solution_infos.append((solution, [], [], []))
|
|
47
45
|
continue
|
|
48
|
-
|
|
49
46
|
mpb, fpr, mps = self._collect_model_paths(models_dir)
|
|
50
47
|
solution_infos.append((solution, mpb, fpr, mps))
|
|
51
48
|
all_mpb_paths.extend(str(p) for p in mpb)
|
|
@@ -61,20 +58,18 @@ class SSolutionBuilder:
|
|
|
61
58
|
|
|
62
59
|
for model_path in fpr_dirs:
|
|
63
60
|
try:
|
|
64
|
-
model =
|
|
65
|
-
|
|
61
|
+
model = parse_fpr(model_path)
|
|
62
|
+
if model is not None:
|
|
63
|
+
solution.models.append(model)
|
|
66
64
|
except Exception as exc:
|
|
67
|
-
import warnings
|
|
68
|
-
|
|
69
65
|
warnings.warn(f"Failed to parse {model_path}: {exc}")
|
|
70
66
|
|
|
71
67
|
for model_path in mps_paths:
|
|
72
68
|
try:
|
|
73
|
-
model =
|
|
74
|
-
|
|
69
|
+
model = parse_mps(model_path)
|
|
70
|
+
if model is not None:
|
|
71
|
+
solution.models.append(model)
|
|
75
72
|
except Exception as exc:
|
|
76
|
-
import warnings
|
|
77
|
-
|
|
78
73
|
warnings.warn(f"Failed to parse {model_path}: {exc}")
|
|
79
74
|
|
|
80
75
|
solutions.append(solution)
|