mps-cli 0.4.0__tar.gz → 0.5.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.4.0 → mps_cli-0.5.1}/.gitignore +7 -1
- {mps_cli-0.4.0 → mps_cli-0.5.1}/PKG-INFO +4 -3
- {mps_cli-0.4.0 → mps_cli-0.5.1}/README.md +2 -1
- {mps_cli-0.4.0 → mps_cli-0.5.1}/Readme.md +2 -1
- {mps_cli-0.4.0 → mps_cli-0.5.1}/pyproject.toml +8 -2
- mps_cli-0.5.1/src/mpscli/demo.py +296 -0
- mps_cli-0.5.1/src/mpscli/demo_language_extraction.py +265 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SConcept.py +1 -2
- mps_cli-0.5.1/src/mpscli/model/SLanguage.py +29 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SModel.py +2 -1
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SNodeRef.py +2 -5
- mps_cli-0.5.1/src/mpscli/model/builder/SLanguageBuilder.py +105 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderBase.py +22 -10
- mps_cli-0.5.1/src/mpscli/model/builder/SModelBuilderBinaryPersistency.py +255 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderDefaultPersistency.py +1 -1
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderFilePerRootPersistency.py +1 -1
- mps_cli-0.5.1/src/mpscli/model/builder/SSolutionBuilder.py +100 -0
- mps_cli-0.5.1/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +126 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/constants.py +55 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/imports.py +79 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/model_input_stream.py +244 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/module_refs.py +10 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/nodes.py +252 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/registry.py +126 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/used_languages.py +36 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/utils.py +36 -0
- mps_cli-0.5.1/src/mpscli/model/builder/binary/uuid_utils.py +16 -0
- mps_cli-0.5.1/src/mpscli/model/builder/utils/JarUtils.py +20 -0
- mps_cli-0.5.1/src/mpscli/model/builder/utils/ModelCache.py +66 -0
- mps_cli-0.5.1/src/mpscli/model/builder/utils/MpbBatchParser.py +124 -0
- mps_cli-0.5.1/src/mpscli/model/builder/utils/NodeIdEncodingUtils.py +54 -0
- mps_cli-0.5.1/tests/binary/Readme.md +7 -0
- mps_cli-0.5.1/tests/binary/test_binary_cross_module_ref.py +49 -0
- mps_cli-0.5.1/tests/binary/test_binary_format_parity.py +71 -0
- mps_cli-0.5.1/tests/binary/test_binary_header.py +36 -0
- mps_cli-0.5.1/tests/binary/test_binary_imports.py +30 -0
- mps_cli-0.5.1/tests/binary/test_binary_language_from_mpl.py +84 -0
- mps_cli-0.5.1/tests/binary/test_binary_library_top.py +85 -0
- mps_cli-0.5.1/tests/binary/test_binary_model_imports.py +25 -0
- mps_cli-0.5.1/tests/binary/test_binary_node_tree.py +126 -0
- mps_cli-0.5.1/tests/binary/test_binary_performance.py +20 -0
- mps_cli-0.5.1/tests/binary/test_binary_persistency_low_level_access.py +250 -0
- mps_cli-0.5.1/tests/binary/test_binary_references.py +118 -0
- mps_cli-0.5.1/tests/binary/test_binary_registry.py +53 -0
- mps_cli-0.5.1/tests/binary/test_binary_repository.py +52 -0
- mps_cli-0.5.1/tests/binary/test_binary_repository_completeness.py +32 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/test_base.py +6 -3
- mps_cli-0.5.1/tests/test_language_extraction.py +43 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/test_model_attributes.py +14 -9
- mps_cli-0.5.1/tests/test_model_imports.py +30 -0
- mps_cli-0.5.1/tests/test_modules_and_models.py +76 -0
- mps_cli-0.5.1/tests/test_node_id_encoding_utils.py +22 -0
- mps_cli-0.5.1/tests/test_nodes.py +150 -0
- mps_cli-0.4.0/src/mpscli/demo.py +0 -7
- mps_cli-0.4.0/src/mpscli/model/SLanguage.py +0 -14
- mps_cli-0.4.0/src/mpscli/model/builder/NodeIdEncodingUtils.py +0 -24
- mps_cli-0.4.0/src/mpscli/model/builder/SLanguageBuilder.py +0 -46
- mps_cli-0.4.0/src/mpscli/model/builder/SSolutionBuilder.py +0 -36
- mps_cli-0.4.0/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +0 -60
- mps_cli-0.4.0/tests/test_language_extraction.py +0 -37
- mps_cli-0.4.0/tests/test_modules_and_models.py +0 -54
- mps_cli-0.4.0/tests/test_node_id_encoding_utils.py +0 -16
- mps_cli-0.4.0/tests/test_nodes.py +0 -58
- mps_cli-0.4.0/tests/test_string_utils.py +0 -16
- {mps_cli-0.4.0 → mps_cli-0.5.1}/LICENSE +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/__init__.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SNode.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SRepository.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SSolution.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/__init__.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/__init__.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/__init__.py +0 -0
- {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/context.py +0 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# --- VS Code Shared Settings ---
|
|
2
|
+
**/.vscode/*
|
|
3
|
+
!mps-cli-py/.vscode/settings.json
|
|
4
|
+
!mps-cli-py/.vscode/extensions.json
|
|
5
|
+
|
|
1
6
|
**/.idea/
|
|
2
7
|
**/.gradle/
|
|
3
8
|
**/build/
|
|
@@ -19,4 +24,5 @@
|
|
|
19
24
|
gradle.properties
|
|
20
25
|
**/workspace.xml
|
|
21
26
|
**/misc.xml
|
|
22
|
-
**/__pycache__/
|
|
27
|
+
**/__pycache__/
|
|
28
|
+
**/.DS_Store
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mps-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
7
|
-
Author-email: Daniel Ratiu <ratiud@googlemail.com>
|
|
7
|
+
Author-email: Daniel Ratiu <ratiud@googlemail.com>, Prithvi Venkat <prithvi.rvce@gmail.com>, Michael Langhammer <mich.langhammer@googlemail.com>
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Classifier: License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -18,7 +18,7 @@ This project provides a Python library which parses MPS files and builds the obj
|
|
|
18
18
|
|
|
19
19
|
### Features
|
|
20
20
|
The following features are available:
|
|
21
|
-
- load MPS files (*.mpsr, *.mps, *.jar) and expose their content as Python object model
|
|
21
|
+
- load MPS files (*.mpsr, *.mps, *.mpb, *.jar) and expose their content as Python object model
|
|
22
22
|
- solutions, models, root nodes, nodes, children, references, properties
|
|
23
23
|
- extract the meta-information and expose it as Python object model
|
|
24
24
|
- list of languages, their concepts with information about properties, references, children
|
|
@@ -32,6 +32,7 @@ The core of the Python object model is given by the following classes:
|
|
|
32
32
|
### Limitations
|
|
33
33
|
The library has currently the following limitations:
|
|
34
34
|
- the recovered language information reflects only the used language in the loaded solutions
|
|
35
|
+
- if "mps/plugins" directory is considered the language information is loaded as an object model
|
|
35
36
|
|
|
36
37
|
### Run tests
|
|
37
38
|
|
|
@@ -4,7 +4,7 @@ This project provides a Python library which parses MPS files and builds the obj
|
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
The following features are available:
|
|
7
|
-
- load MPS files (*.mpsr, *.mps, *.jar) and expose their content as Python object model
|
|
7
|
+
- load MPS files (*.mpsr, *.mps, *.mpb, *.jar) and expose their content as Python object model
|
|
8
8
|
- solutions, models, root nodes, nodes, children, references, properties
|
|
9
9
|
- extract the meta-information and expose it as Python object model
|
|
10
10
|
- list of languages, their concepts with information about properties, references, children
|
|
@@ -18,6 +18,7 @@ The core of the Python object model is given by the following classes:
|
|
|
18
18
|
### Limitations
|
|
19
19
|
The library has currently the following limitations:
|
|
20
20
|
- the recovered language information reflects only the used language in the loaded solutions
|
|
21
|
+
- if "mps/plugins" directory is considered the language information is loaded as an object model
|
|
21
22
|
|
|
22
23
|
### Run tests
|
|
23
24
|
|
|
@@ -4,7 +4,7 @@ This project provides a Python library which parses MPS files and builds the obj
|
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
The following features are available:
|
|
7
|
-
- load MPS files (*.mpsr, *.mps, *.jar) and expose their content as Python object model
|
|
7
|
+
- load MPS files (*.mpsr, *.mps, *.mpb, *.jar) and expose their content as Python object model
|
|
8
8
|
- solutions, models, root nodes, nodes, children, references, properties
|
|
9
9
|
- extract the meta-information and expose it as Python object model
|
|
10
10
|
- list of languages, their concepts with information about properties, references, children
|
|
@@ -18,6 +18,7 @@ The core of the Python object model is given by the following classes:
|
|
|
18
18
|
### Limitations
|
|
19
19
|
The library has currently the following limitations:
|
|
20
20
|
- the recovered language information reflects only the used language in the loaded solutions
|
|
21
|
+
- if "mps/plugins" directory is considered the language information is loaded as an object model
|
|
21
22
|
|
|
22
23
|
### Run tests
|
|
23
24
|
|
|
@@ -4,9 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mps-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Daniel Ratiu", email="ratiud@googlemail.com" },
|
|
10
|
+
{ name="Prithvi Venkat", email="prithvi.rvce@gmail.com" },
|
|
11
|
+
{ name="Michael Langhammer", email="mich.langhammer@googlemail.com" },
|
|
10
12
|
]
|
|
11
13
|
description = "MPS Command Line Interface (MPS-CLI) is a library to parse MPS files and build the model as Python objects."
|
|
12
14
|
readme = "README.md"
|
|
@@ -22,4 +24,8 @@ classifiers = [
|
|
|
22
24
|
"Bug Tracker" = "https://github.com/mbeddr/mps-cli/issues"
|
|
23
25
|
|
|
24
26
|
[tool.hatch.build.targets.wheel]
|
|
25
|
-
packages = ["src/mpscli"]
|
|
27
|
+
packages = ["src/mpscli"]
|
|
28
|
+
|
|
29
|
+
[tool.black]
|
|
30
|
+
line-length = 88
|
|
31
|
+
target-version = ['py37']
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
demo.py - A quick local test for the binary parser.
|
|
3
|
+
|
|
4
|
+
Points at a local MPS project directory and prints a summary of what was parsed.
|
|
5
|
+
Edit PLUGINS_PATH below to point at either MPS plugins folder or a test project.
|
|
6
|
+
|
|
7
|
+
Two modes:
|
|
8
|
+
MODE = "plugins": parse a full MPS plugins directory (e.g. C:\\Temp\\plugins)
|
|
9
|
+
MODE = "test_project": parse a single mps_test_projects directory (original behaviour)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(1, "..")
|
|
18
|
+
|
|
19
|
+
# "plugins" or "test_project"
|
|
20
|
+
MODE = "plugins"
|
|
21
|
+
PLUGINS_PATH = r"C:\Temp\plugins"
|
|
22
|
+
TEST_PROJECT = r"..\..\mps_test_projects\mps_cli_lanuse_file_per_root"
|
|
23
|
+
|
|
24
|
+
from mpscli.model.builder.SSolutionsRepositoryBuilder import SSolutionsRepositoryBuilder
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def count_nodes(node) -> int:
|
|
28
|
+
total = 1
|
|
29
|
+
for child in node.children or []:
|
|
30
|
+
total += count_nodes(child)
|
|
31
|
+
return total
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def concept_name(concept) -> str:
|
|
35
|
+
if concept is None:
|
|
36
|
+
return "<null>"
|
|
37
|
+
if isinstance(concept, str):
|
|
38
|
+
return concept
|
|
39
|
+
full = getattr(concept, "name", None)
|
|
40
|
+
if full:
|
|
41
|
+
return full.split(".")[-1]
|
|
42
|
+
return str(concept)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def concept_full_name(concept) -> str:
|
|
46
|
+
if concept is None:
|
|
47
|
+
return "<null>"
|
|
48
|
+
if isinstance(concept, str):
|
|
49
|
+
return concept
|
|
50
|
+
return getattr(concept, "name", str(concept))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def fmt_id(value) -> str:
|
|
54
|
+
return str(value) if value is not None else "null"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def print_node(node, depth: int = 0, max_depth: int = 3) -> None:
|
|
58
|
+
if depth > max_depth:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
pad = " " * depth
|
|
62
|
+
cname = concept_name(node.concept)
|
|
63
|
+
role = getattr(node, "role_in_parent", None)
|
|
64
|
+
uid = fmt_id(getattr(node, "uuid", None))
|
|
65
|
+
name = node.properties.get("name", "")
|
|
66
|
+
|
|
67
|
+
# node headline: bullet + concept + role slot + name in quotes + id in brackets
|
|
68
|
+
role_tag = f" [{role}]" if role else ""
|
|
69
|
+
name_tag = f" '{name}'" if name else ""
|
|
70
|
+
print(f"{pad}\u2022 {cname}{role_tag}{name_tag} [{uid}]")
|
|
71
|
+
|
|
72
|
+
# other properties (name already shown inline above)
|
|
73
|
+
for k, v in sorted((node.properties or {}).items()):
|
|
74
|
+
if k != "name":
|
|
75
|
+
print(f"{pad} {k} = {v!r}")
|
|
76
|
+
|
|
77
|
+
# references
|
|
78
|
+
for k, ref in sorted((node.references or {}).items()):
|
|
79
|
+
resolve = getattr(ref, "resolve_info", None) or ""
|
|
80
|
+
model_u = str(getattr(ref, "model_uuid", "") or "")
|
|
81
|
+
if model_u.startswith("java:"):
|
|
82
|
+
# java stub reference - show class name from resolve_info
|
|
83
|
+
print(f"{pad} {k} -> {resolve} (Java)")
|
|
84
|
+
else:
|
|
85
|
+
# mps model reference - show uuid + resolve hint
|
|
86
|
+
hint = f" ({resolve})" if resolve else ""
|
|
87
|
+
print(f"{pad} {k} -> {model_u[:36]}{hint}")
|
|
88
|
+
|
|
89
|
+
for child in node.children or []:
|
|
90
|
+
print_node(child, depth + 1, max_depth)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def print_model(model, max_roots: int = 3, max_depth: int = 3) -> None:
|
|
94
|
+
roots = model.root_nodes
|
|
95
|
+
total_nodes = sum(count_nodes(r) for r in roots)
|
|
96
|
+
|
|
97
|
+
print(f"\n--- Model: {model.name} ---")
|
|
98
|
+
print(f" uuid : {model.uuid}")
|
|
99
|
+
print(f" root nodes : {len(roots)}")
|
|
100
|
+
print(f" total nodes: {total_nodes}")
|
|
101
|
+
|
|
102
|
+
for i, root in enumerate(roots[:max_roots]):
|
|
103
|
+
print(f"\n [ROOT {i}]")
|
|
104
|
+
print_node(root, depth=2, max_depth=max_depth)
|
|
105
|
+
|
|
106
|
+
if len(roots) > max_roots:
|
|
107
|
+
print(f"\n ... ({len(roots) - max_roots} more root nodes not shown)")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def verify_model(model) -> dict:
|
|
111
|
+
checks = {}
|
|
112
|
+
|
|
113
|
+
checks["uuid_set"] = (
|
|
114
|
+
model.uuid not in ("r:unknown", "", None),
|
|
115
|
+
str(model.uuid),
|
|
116
|
+
)
|
|
117
|
+
checks["name_set"] = (
|
|
118
|
+
model.name not in ("unknown.model", "", None),
|
|
119
|
+
str(model.name),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
bad_concepts = [
|
|
123
|
+
concept_full_name(r.concept) for r in model.root_nodes if not r.concept
|
|
124
|
+
]
|
|
125
|
+
checks["concepts_set"] = (
|
|
126
|
+
not bad_concepts,
|
|
127
|
+
f"{len(bad_concepts)} missing" if bad_concepts else "all set",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
null_ids = sum(1 for r in model.root_nodes if getattr(r, "uuid", None) is None)
|
|
131
|
+
checks["node_ids_set"] = (
|
|
132
|
+
null_ids == 0,
|
|
133
|
+
f"{null_ids} null ids" if null_ids else "all set",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
bad_props = sum(
|
|
137
|
+
1
|
|
138
|
+
for r in model.root_nodes
|
|
139
|
+
if not isinstance(getattr(r, "properties", None), dict)
|
|
140
|
+
)
|
|
141
|
+
checks["properties_are_dict"] = (
|
|
142
|
+
bad_props == 0,
|
|
143
|
+
f"{bad_props} nodes with bad props" if bad_props else "all dicts",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
bad_refs = sum(
|
|
147
|
+
1
|
|
148
|
+
for r in model.root_nodes
|
|
149
|
+
if not isinstance(getattr(r, "references", None), dict)
|
|
150
|
+
)
|
|
151
|
+
checks["references_are_dict"] = (
|
|
152
|
+
bad_refs == 0,
|
|
153
|
+
f"{bad_refs} nodes with bad refs" if bad_refs else "all dicts",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return checks
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def print_languages(repo) -> None:
|
|
160
|
+
# split languagess into two groups so basically those with aspect models loaded (mpl was actuallyy found) and
|
|
161
|
+
# those without (maybe only seen via registry during .mpb parsing)
|
|
162
|
+
with_models = [l for l in repo.languages if l.models]
|
|
163
|
+
without_models = [l for l in repo.languages if not l.models]
|
|
164
|
+
|
|
165
|
+
print(f"\n*** Languages ({len(repo.languages)} total) ***")
|
|
166
|
+
print(f" with aspect models loaded - {len(with_models)}")
|
|
167
|
+
print(f" Registry-only (no .mpl found) : {len(without_models)}")
|
|
168
|
+
|
|
169
|
+
if with_models:
|
|
170
|
+
print("\n ---Languages with aspect models ----")
|
|
171
|
+
for lang in sorted(with_models, key=lambda l: l.name):
|
|
172
|
+
aspect_names = ", ".join(
|
|
173
|
+
m.name.split(".")[-1] for m in lang.models if m.name
|
|
174
|
+
)
|
|
175
|
+
print(f" \u2022 {lang.name}")
|
|
176
|
+
print(f" version : {lang.language_version}")
|
|
177
|
+
print(f" uuid : {lang.uuid}")
|
|
178
|
+
print(f" aspects : {aspect_names}")
|
|
179
|
+
print(f" concepts (registry): {len(lang.concepts)}")
|
|
180
|
+
|
|
181
|
+
# show rooot node countt per aspect to verify .mpb parsing actually worked
|
|
182
|
+
for m in lang.models:
|
|
183
|
+
aspect = m.name.split(".")[-1] if m.name else "?"
|
|
184
|
+
root_count = len(m.root_nodes)
|
|
185
|
+
print(f"[{aspect}] {root_count} roots")
|
|
186
|
+
|
|
187
|
+
if "structure" in (m.name or "") and root_count > 0:
|
|
188
|
+
concept_names = [
|
|
189
|
+
r.properties.get("name", "")
|
|
190
|
+
for r in m.root_nodes[:5]
|
|
191
|
+
if r.properties.get("name")
|
|
192
|
+
]
|
|
193
|
+
if concept_names:
|
|
194
|
+
print(f"sample concepts: {', '.join(concept_names)}")
|
|
195
|
+
|
|
196
|
+
if without_models:
|
|
197
|
+
print("\n ---- Registry-only languages (no aspect models) ---")
|
|
198
|
+
for lang in sorted(without_models, key=lambda l: l.name):
|
|
199
|
+
print(f" \u2022 {lang.name} ({len(lang.concepts)} concepts)")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def main() -> None:
|
|
203
|
+
path = PLUGINS_PATH if MODE == "plugins" else TEST_PROJECT
|
|
204
|
+
|
|
205
|
+
builder = SSolutionsRepositoryBuilder()
|
|
206
|
+
|
|
207
|
+
msg = f"Parsing ({MODE}): {path}"
|
|
208
|
+
print(msg, flush=True)
|
|
209
|
+
sys.stderr.write(msg + "\n")
|
|
210
|
+
sys.stderr.flush()
|
|
211
|
+
|
|
212
|
+
t0 = time.perf_counter()
|
|
213
|
+
repo = builder.build(path)
|
|
214
|
+
t1 = time.perf_counter()
|
|
215
|
+
|
|
216
|
+
elapsed = t1 - t0
|
|
217
|
+
timing_msg = f"Parsing complete in {elapsed:.1f}s"
|
|
218
|
+
print(f"\n{timing_msg}")
|
|
219
|
+
sys.stderr.write(timing_msg + "\n")
|
|
220
|
+
sys.stderr.flush()
|
|
221
|
+
|
|
222
|
+
total_models = sum(len(s.models) for s in repo.solutions)
|
|
223
|
+
total_nodes = sum(
|
|
224
|
+
sum(count_nodes(r) for r in m.root_nodes)
|
|
225
|
+
for s in repo.solutions
|
|
226
|
+
for m in s.models
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
summary = (
|
|
230
|
+
f"\n***Repository Summary***\n"
|
|
231
|
+
f" Solutions : {len(repo.solutions)}\n"
|
|
232
|
+
f" Languages : {len(repo.languages)}\n"
|
|
233
|
+
f" Models : {total_models}\n"
|
|
234
|
+
f" Nodes : {total_nodes:,}\n"
|
|
235
|
+
)
|
|
236
|
+
print(summary)
|
|
237
|
+
sys.stderr.write(summary)
|
|
238
|
+
sys.stderr.flush()
|
|
239
|
+
|
|
240
|
+
print("=== Verification ===")
|
|
241
|
+
failed = 0
|
|
242
|
+
for sol in repo.solutions:
|
|
243
|
+
for model in sol.models:
|
|
244
|
+
checks = verify_model(model)
|
|
245
|
+
bad = [(k, detail) for k, (passed, detail) in checks.items() if not passed]
|
|
246
|
+
if bad:
|
|
247
|
+
failed += 1
|
|
248
|
+
print(f" FAIL {model.name}")
|
|
249
|
+
for k, detail in bad:
|
|
250
|
+
print(f" [{k}] {detail}")
|
|
251
|
+
|
|
252
|
+
verdict = (
|
|
253
|
+
f" All {total_models} models passed verification"
|
|
254
|
+
if failed == 0
|
|
255
|
+
else f" {failed} models failed verification"
|
|
256
|
+
)
|
|
257
|
+
print(verdict)
|
|
258
|
+
sys.stderr.write(f"\n{verdict}\n")
|
|
259
|
+
sys.stderr.flush()
|
|
260
|
+
|
|
261
|
+
# language extension verification - shows whether .mpl reading worked
|
|
262
|
+
print_languages(repo)
|
|
263
|
+
|
|
264
|
+
print("\n=== Solutions ===")
|
|
265
|
+
for sol in repo.solutions:
|
|
266
|
+
if not sol.models:
|
|
267
|
+
# skip language-only jars with no models to show
|
|
268
|
+
continue
|
|
269
|
+
sol_nodes = sum(sum(count_nodes(r) for r in m.root_nodes) for m in sol.models)
|
|
270
|
+
print(f"\n{'='*60}")
|
|
271
|
+
print(f"Solution : {sol.name}")
|
|
272
|
+
print(f"Models : {len(sol.models)} Nodes: {sol_nodes:,}")
|
|
273
|
+
print(f"{'='*60}")
|
|
274
|
+
for model in sol.models:
|
|
275
|
+
print_model(model, max_roots=3, max_depth=3)
|
|
276
|
+
|
|
277
|
+
print("\n=== Languages ===")
|
|
278
|
+
for lan in repo.languages:
|
|
279
|
+
if not lan.concepts:
|
|
280
|
+
continue
|
|
281
|
+
print(f"Language : {lan.name}")
|
|
282
|
+
print(f"{'='*60}")
|
|
283
|
+
for c in lan.concepts:
|
|
284
|
+
c.print_concept_details()
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
import multiprocessing
|
|
288
|
+
|
|
289
|
+
multiprocessing.freeze_support()
|
|
290
|
+
|
|
291
|
+
# redirect stdout to a log file
|
|
292
|
+
log_file = f"mps_debug_{datetime.now().strftime('%H%M%S')}.log"
|
|
293
|
+
sys.stdout = open(log_file, "w", encoding="utf-8", buffering=1)
|
|
294
|
+
print(f"Logging to: {log_file}")
|
|
295
|
+
|
|
296
|
+
main()
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
demo_language_extraction.py - extract concept definitions from language structure aspects and
|
|
3
|
+
write them as a markdown file that is transferrable to HTML
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(1, "..")
|
|
12
|
+
|
|
13
|
+
MODE = "plugins"
|
|
14
|
+
PLUGINS_PATH = r"C:\Temp\plugins"
|
|
15
|
+
TEST_PROJECT = r"..\..\mps_test_projects\mps_cli_binary_persistency_language"
|
|
16
|
+
OUTPUT_FILE = Path(f"language_concepts_{datetime.now().strftime('%H%M%S')}.md")
|
|
17
|
+
|
|
18
|
+
from mpscli.model.builder.SSolutionsRepositoryBuilder import SSolutionsRepositoryBuilder
|
|
19
|
+
from mpscli.model.builder.SLanguageBuilder import SLanguageBuilder
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _short(full_name: str) -> str:
|
|
23
|
+
if not full_name:
|
|
24
|
+
return ""
|
|
25
|
+
return full_name.split(".")[-1]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _prop(node, key: str, default="") -> str:
|
|
29
|
+
props = getattr(node, "properties", None) or {}
|
|
30
|
+
return props.get(key, default).strip()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _ref_resolve_info(ref_obj) -> str:
|
|
34
|
+
return (getattr(ref_obj, "resolve_info", None) or "").strip()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def extract_concept_info(concept_node) -> dict:
|
|
38
|
+
# pull all useful fields from a ConceptDeclaration or InterfaceConceptDeclaration SNode
|
|
39
|
+
info = {}
|
|
40
|
+
|
|
41
|
+
info["name"] = _prop(concept_node, "name") or _short(
|
|
42
|
+
str(getattr(concept_node, "concept", ""))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
info["concept_type"] = _short(str(getattr(concept_node, "concept", "") or ""))
|
|
46
|
+
info["is_abstract"] = _prop(concept_node, "abstract") == "true"
|
|
47
|
+
info["is_root"] = _prop(concept_node, "rootable") == "true"
|
|
48
|
+
info["alias"] = _prop(concept_node, "alias")
|
|
49
|
+
info["short_description"] = _prop(concept_node, "shortDescription")
|
|
50
|
+
|
|
51
|
+
# supertype is a direct reference on the root node
|
|
52
|
+
refs = getattr(concept_node, "references", None) or {}
|
|
53
|
+
extends_ref = refs.get("extends") or refs.get("superConcept")
|
|
54
|
+
info["extends"] = _ref_resolve_info(extends_ref) if extends_ref else ""
|
|
55
|
+
|
|
56
|
+
implements = []
|
|
57
|
+
properties = []
|
|
58
|
+
child_links = []
|
|
59
|
+
ref_links = []
|
|
60
|
+
|
|
61
|
+
for child in getattr(concept_node, "children", None) or []:
|
|
62
|
+
role = getattr(child, "role_in_parent", "") or ""
|
|
63
|
+
child_refs = getattr(child, "references", None) or {}
|
|
64
|
+
|
|
65
|
+
if role == "implements":
|
|
66
|
+
ri = (
|
|
67
|
+
_ref_resolve_info(child_refs.get("intfc"))
|
|
68
|
+
if "intfc" in child_refs
|
|
69
|
+
else ""
|
|
70
|
+
)
|
|
71
|
+
if ri:
|
|
72
|
+
implements.append(ri)
|
|
73
|
+
|
|
74
|
+
elif role == "propertyDeclaration":
|
|
75
|
+
prop_name = _prop(child, "name")
|
|
76
|
+
prop_type = (
|
|
77
|
+
_ref_resolve_info(child_refs.get("dataType"))
|
|
78
|
+
if "dataType" in child_refs
|
|
79
|
+
else ""
|
|
80
|
+
)
|
|
81
|
+
if prop_name:
|
|
82
|
+
properties.append({"name": prop_name, "type": prop_type})
|
|
83
|
+
|
|
84
|
+
elif role == "linkDeclaration":
|
|
85
|
+
meta = _prop(child, "metaClass")
|
|
86
|
+
link_name = _prop(child, "role")
|
|
87
|
+
raw_card = _prop(child, "sourceCardinality")
|
|
88
|
+
cardinality = raw_card.split("/")[-1] if "/" in raw_card else raw_card
|
|
89
|
+
link_type = (
|
|
90
|
+
_ref_resolve_info(child_refs.get("target"))
|
|
91
|
+
if "target" in child_refs
|
|
92
|
+
else ""
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not link_name:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
entry = {"name": link_name, "type": link_type, "cardinality": cardinality}
|
|
99
|
+
if "aggregation" in meta:
|
|
100
|
+
child_links.append(entry)
|
|
101
|
+
elif "reference" in meta:
|
|
102
|
+
ref_links.append(entry)
|
|
103
|
+
|
|
104
|
+
info["implements"] = implements
|
|
105
|
+
info["properties"] = properties
|
|
106
|
+
info["child_links"] = child_links
|
|
107
|
+
info["ref_links"] = ref_links
|
|
108
|
+
|
|
109
|
+
return info
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _format_concept(info: dict) -> str:
|
|
113
|
+
# produces the plain-text declaration block usedd for .md file
|
|
114
|
+
lines = []
|
|
115
|
+
|
|
116
|
+
prefix = "abstract " if info["is_abstract"] else ""
|
|
117
|
+
concept_kind = (
|
|
118
|
+
"interface concept" if "Interface" in info["concept_type"] else "concept"
|
|
119
|
+
)
|
|
120
|
+
header = f"{prefix}{concept_kind} {info['name']}"
|
|
121
|
+
|
|
122
|
+
extends_part = f"extends {_short(info['extends'])}" if info["extends"] else ""
|
|
123
|
+
impl_parts = [_short(i) for i in info["implements"]]
|
|
124
|
+
|
|
125
|
+
if extends_part and impl_parts:
|
|
126
|
+
lines.append(f"{header} - {extends_part}")
|
|
127
|
+
pad = " " * (len(header) + 3)
|
|
128
|
+
for iface in impl_parts:
|
|
129
|
+
lines.append(f"{pad}implements {iface}")
|
|
130
|
+
elif extends_part:
|
|
131
|
+
lines.append(f"{header} - {extends_part}")
|
|
132
|
+
elif impl_parts:
|
|
133
|
+
lines.append(f"{header} - implements {impl_parts[0]}")
|
|
134
|
+
pad = " " * (len(header) + 3)
|
|
135
|
+
for iface in impl_parts[1:]:
|
|
136
|
+
lines.append(f"{pad}implements {iface}")
|
|
137
|
+
else:
|
|
138
|
+
lines.append(header)
|
|
139
|
+
|
|
140
|
+
if info["alias"] or info["short_description"] or info["is_root"]:
|
|
141
|
+
lines.append("")
|
|
142
|
+
if info["alias"]:
|
|
143
|
+
lines.append(f"alias: {info['alias']}")
|
|
144
|
+
if info["short_description"]:
|
|
145
|
+
lines.append(f"description: {info['short_description']}")
|
|
146
|
+
if info["is_root"]:
|
|
147
|
+
lines.append("instance can be root: yes")
|
|
148
|
+
|
|
149
|
+
if info["properties"]:
|
|
150
|
+
lines.append("")
|
|
151
|
+
lines.append("properties:")
|
|
152
|
+
for p in info["properties"]:
|
|
153
|
+
t = f" : {_short(p['type'])}" if p["type"] else ""
|
|
154
|
+
lines.append(f" {p['name']}{t}")
|
|
155
|
+
|
|
156
|
+
if info["child_links"]:
|
|
157
|
+
lines.append("")
|
|
158
|
+
lines.append("children:")
|
|
159
|
+
for c in info["child_links"]:
|
|
160
|
+
t = f" : {_short(c['type'])}" if c["type"] else ""
|
|
161
|
+
card = f" [{c['cardinality']}]" if c["cardinality"] else ""
|
|
162
|
+
lines.append(f" {c['name']}{t}{card}")
|
|
163
|
+
|
|
164
|
+
if info["ref_links"]:
|
|
165
|
+
lines.append("")
|
|
166
|
+
lines.append("references:")
|
|
167
|
+
for r in info["ref_links"]:
|
|
168
|
+
t = f" : {_short(r['type'])}" if r["type"] else ""
|
|
169
|
+
card = f" [{r['cardinality']}]" if r["cardinality"] else ""
|
|
170
|
+
lines.append(f" {r['name']}{t}{card}")
|
|
171
|
+
|
|
172
|
+
return "\n".join(lines)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def collect_languages_with_structure() -> list:
|
|
176
|
+
result = []
|
|
177
|
+
for lang in sorted(SLanguageBuilder.languages.values(), key=lambda l: l.name):
|
|
178
|
+
if not lang.models:
|
|
179
|
+
continue
|
|
180
|
+
structure = lang.find_model_by_name("structure")
|
|
181
|
+
if structure and structure.root_nodes:
|
|
182
|
+
result.append((lang, structure))
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def build_markdown(languages_with_structure: list) -> str:
|
|
187
|
+
# the markdown uses fencedd code blocks so thhe aligned plain text declaration renders in monospace
|
|
188
|
+
# in HTML but is still clean as raww text
|
|
189
|
+
sections = []
|
|
190
|
+
|
|
191
|
+
sections.append("# Language Concept Definitions")
|
|
192
|
+
sections.append("")
|
|
193
|
+
sections.append("only languages for which a .mpl source was found are included.")
|
|
194
|
+
sections.append("")
|
|
195
|
+
|
|
196
|
+
if not languages_with_structure:
|
|
197
|
+
sections.append("No structure aspect models were found.")
|
|
198
|
+
return "\n".join(sections)
|
|
199
|
+
|
|
200
|
+
for lang, structure in languages_with_structure:
|
|
201
|
+
sections.append(f"## {lang.name}")
|
|
202
|
+
sections.append("")
|
|
203
|
+
sections.append(f"uuid: {lang.uuid} ")
|
|
204
|
+
sections.append(f"version: {lang.language_version} ")
|
|
205
|
+
sections.append(f"concepts: {len(structure.root_nodes)}")
|
|
206
|
+
sections.append("")
|
|
207
|
+
|
|
208
|
+
for root in structure.root_nodes:
|
|
209
|
+
cname = _short(str(getattr(root, "concept", "") or ""))
|
|
210
|
+
if "concept" not in cname.lower() and "Concept" not in cname:
|
|
211
|
+
continue
|
|
212
|
+
info = extract_concept_info(root)
|
|
213
|
+
if not info["name"]:
|
|
214
|
+
continue
|
|
215
|
+
# fenced code block preserves alignment and I think renders cleanly in HTML
|
|
216
|
+
sections.append("```")
|
|
217
|
+
sections.append(_format_concept(info))
|
|
218
|
+
sections.append("```")
|
|
219
|
+
sections.append("")
|
|
220
|
+
|
|
221
|
+
return "\n".join(sections)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def main():
|
|
225
|
+
path = PLUGINS_PATH if MODE == "plugins" else TEST_PROJECT
|
|
226
|
+
|
|
227
|
+
print(f"Parsing ({MODE}): {path}", flush=True)
|
|
228
|
+
builder = SSolutionsRepositoryBuilder()
|
|
229
|
+
builder.USE_CACHE = False
|
|
230
|
+
builder.build(path)
|
|
231
|
+
|
|
232
|
+
languages_with_structure = collect_languages_with_structure()
|
|
233
|
+
|
|
234
|
+
print(
|
|
235
|
+
f"Languages with structure aspect: {len(languages_with_structure)}", flush=True
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
for lang, structure in languages_with_structure:
|
|
239
|
+
print(f"\n{'=' * 60}")
|
|
240
|
+
print(f"Language : {lang.name}")
|
|
241
|
+
print(f"uuid : {lang.uuid}")
|
|
242
|
+
print(f"version : {lang.language_version}")
|
|
243
|
+
print(f"concepts : {len(structure.root_nodes)}")
|
|
244
|
+
print(f"{'=' * 60}")
|
|
245
|
+
print("")
|
|
246
|
+
|
|
247
|
+
for root in structure.root_nodes:
|
|
248
|
+
cname = _short(str(getattr(root, "concept", "") or ""))
|
|
249
|
+
if "concept" not in cname.lower() and "Concept" not in cname:
|
|
250
|
+
continue
|
|
251
|
+
info = extract_concept_info(root)
|
|
252
|
+
if not info["name"]:
|
|
253
|
+
continue
|
|
254
|
+
# print each line indented by two spaces for readabilityy
|
|
255
|
+
for line in _format_concept(info).splitlines():
|
|
256
|
+
print(f" {line}")
|
|
257
|
+
print("")
|
|
258
|
+
|
|
259
|
+
md = build_markdown(languages_with_structure)
|
|
260
|
+
OUTPUT_FILE.write_text(md, encoding="utf-8")
|
|
261
|
+
print(f"\nmarkdown file written: {OUTPUT_FILE}", flush=True)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
main()
|