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.
Files changed (73) hide show
  1. {mps_cli-0.4.0 → mps_cli-0.5.1}/.gitignore +7 -1
  2. {mps_cli-0.4.0 → mps_cli-0.5.1}/PKG-INFO +4 -3
  3. {mps_cli-0.4.0 → mps_cli-0.5.1}/README.md +2 -1
  4. {mps_cli-0.4.0 → mps_cli-0.5.1}/Readme.md +2 -1
  5. {mps_cli-0.4.0 → mps_cli-0.5.1}/pyproject.toml +8 -2
  6. mps_cli-0.5.1/src/mpscli/demo.py +296 -0
  7. mps_cli-0.5.1/src/mpscli/demo_language_extraction.py +265 -0
  8. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SConcept.py +1 -2
  9. mps_cli-0.5.1/src/mpscli/model/SLanguage.py +29 -0
  10. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SModel.py +2 -1
  11. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SNodeRef.py +2 -5
  12. mps_cli-0.5.1/src/mpscli/model/builder/SLanguageBuilder.py +105 -0
  13. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderBase.py +22 -10
  14. mps_cli-0.5.1/src/mpscli/model/builder/SModelBuilderBinaryPersistency.py +255 -0
  15. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderDefaultPersistency.py +1 -1
  16. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/SModelBuilderFilePerRootPersistency.py +1 -1
  17. mps_cli-0.5.1/src/mpscli/model/builder/SSolutionBuilder.py +100 -0
  18. mps_cli-0.5.1/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +126 -0
  19. mps_cli-0.5.1/src/mpscli/model/builder/binary/constants.py +55 -0
  20. mps_cli-0.5.1/src/mpscli/model/builder/binary/imports.py +79 -0
  21. mps_cli-0.5.1/src/mpscli/model/builder/binary/model_input_stream.py +244 -0
  22. mps_cli-0.5.1/src/mpscli/model/builder/binary/module_refs.py +10 -0
  23. mps_cli-0.5.1/src/mpscli/model/builder/binary/nodes.py +252 -0
  24. mps_cli-0.5.1/src/mpscli/model/builder/binary/registry.py +126 -0
  25. mps_cli-0.5.1/src/mpscli/model/builder/binary/used_languages.py +36 -0
  26. mps_cli-0.5.1/src/mpscli/model/builder/binary/utils.py +36 -0
  27. mps_cli-0.5.1/src/mpscli/model/builder/binary/uuid_utils.py +16 -0
  28. mps_cli-0.5.1/src/mpscli/model/builder/utils/JarUtils.py +20 -0
  29. mps_cli-0.5.1/src/mpscli/model/builder/utils/ModelCache.py +66 -0
  30. mps_cli-0.5.1/src/mpscli/model/builder/utils/MpbBatchParser.py +124 -0
  31. mps_cli-0.5.1/src/mpscli/model/builder/utils/NodeIdEncodingUtils.py +54 -0
  32. mps_cli-0.5.1/tests/binary/Readme.md +7 -0
  33. mps_cli-0.5.1/tests/binary/test_binary_cross_module_ref.py +49 -0
  34. mps_cli-0.5.1/tests/binary/test_binary_format_parity.py +71 -0
  35. mps_cli-0.5.1/tests/binary/test_binary_header.py +36 -0
  36. mps_cli-0.5.1/tests/binary/test_binary_imports.py +30 -0
  37. mps_cli-0.5.1/tests/binary/test_binary_language_from_mpl.py +84 -0
  38. mps_cli-0.5.1/tests/binary/test_binary_library_top.py +85 -0
  39. mps_cli-0.5.1/tests/binary/test_binary_model_imports.py +25 -0
  40. mps_cli-0.5.1/tests/binary/test_binary_node_tree.py +126 -0
  41. mps_cli-0.5.1/tests/binary/test_binary_performance.py +20 -0
  42. mps_cli-0.5.1/tests/binary/test_binary_persistency_low_level_access.py +250 -0
  43. mps_cli-0.5.1/tests/binary/test_binary_references.py +118 -0
  44. mps_cli-0.5.1/tests/binary/test_binary_registry.py +53 -0
  45. mps_cli-0.5.1/tests/binary/test_binary_repository.py +52 -0
  46. mps_cli-0.5.1/tests/binary/test_binary_repository_completeness.py +32 -0
  47. {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/test_base.py +6 -3
  48. mps_cli-0.5.1/tests/test_language_extraction.py +43 -0
  49. {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/test_model_attributes.py +14 -9
  50. mps_cli-0.5.1/tests/test_model_imports.py +30 -0
  51. mps_cli-0.5.1/tests/test_modules_and_models.py +76 -0
  52. mps_cli-0.5.1/tests/test_node_id_encoding_utils.py +22 -0
  53. mps_cli-0.5.1/tests/test_nodes.py +150 -0
  54. mps_cli-0.4.0/src/mpscli/demo.py +0 -7
  55. mps_cli-0.4.0/src/mpscli/model/SLanguage.py +0 -14
  56. mps_cli-0.4.0/src/mpscli/model/builder/NodeIdEncodingUtils.py +0 -24
  57. mps_cli-0.4.0/src/mpscli/model/builder/SLanguageBuilder.py +0 -46
  58. mps_cli-0.4.0/src/mpscli/model/builder/SSolutionBuilder.py +0 -36
  59. mps_cli-0.4.0/src/mpscli/model/builder/SSolutionsRepositoryBuilder.py +0 -60
  60. mps_cli-0.4.0/tests/test_language_extraction.py +0 -37
  61. mps_cli-0.4.0/tests/test_modules_and_models.py +0 -54
  62. mps_cli-0.4.0/tests/test_node_id_encoding_utils.py +0 -16
  63. mps_cli-0.4.0/tests/test_nodes.py +0 -58
  64. mps_cli-0.4.0/tests/test_string_utils.py +0 -16
  65. {mps_cli-0.4.0 → mps_cli-0.5.1}/LICENSE +0 -0
  66. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/__init__.py +0 -0
  67. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SNode.py +0 -0
  68. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SRepository.py +0 -0
  69. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/SSolution.py +0 -0
  70. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/__init__.py +0 -0
  71. {mps_cli-0.4.0 → mps_cli-0.5.1}/src/mpscli/model/builder/__init__.py +0 -0
  72. {mps_cli-0.4.0 → mps_cli-0.5.1}/tests/__init__.py +0 -0
  73. {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.4.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.4.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()
@@ -1,4 +1,3 @@
1
-
2
1
  class SConcept:
3
2
 
4
3
  def __init__(self, name, uuid):
@@ -19,4 +18,4 @@ class SConcept:
19
18
  print("\treferences: ")
20
19
  for reference in self.references:
21
20
  print("\t\t" + reference)
22
- print("<<<")
21
+ print("<<<")