mi-mdb 0.1.0__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.
- mi_mdb-0.1.0/LICENSE +21 -0
- mi_mdb-0.1.0/MANIFEST.in +1 -0
- mi_mdb-0.1.0/PKG-INFO +49 -0
- mi_mdb-0.1.0/README.md +2 -0
- mi_mdb-0.1.0/pyproject.toml +30 -0
- mi_mdb-0.1.0/setup.cfg +4 -0
- mi_mdb-0.1.0/src/mdb/__init__.py +1 -0
- mi_mdb-0.1.0/src/mdb/__main__.py +85 -0
- mi_mdb-0.1.0/src/mdb/_dep_system.py +136 -0
- mi_mdb-0.1.0/src/mdb/log.conf +48 -0
- mi_mdb-0.1.0/src/mdb/scenario.py +48 -0
- mi_mdb-0.1.0/src/mdb/session.py +423 -0
- mi_mdb-0.1.0/src/mdb/ui_types.py +4 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/PKG-INFO +49 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/SOURCES.txt +17 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/dependency_links.txt +1 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/entry_points.txt +2 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/requires.txt +11 -0
- mi_mdb-0.1.0/src/mi_mdb.egg-info/top_level.txt +1 -0
mi_mdb-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leon Starr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mi_mdb-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include src/mdb/log.conf
|
mi_mdb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mi-mdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shlaer-Mellor Executable UML Model Debugger
|
|
5
|
+
Author-email: Leon Starr <leon_starr@modelint.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Leon Starr
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: repository, https://github.com/modelint/model-debugger
|
|
29
|
+
Project-URL: documentation, https://github.com/modelint/model-debugger/wiki
|
|
30
|
+
Keywords: shlaer-mellor,metamodel,executable uml,mbse,xuml,xtuml,model debugger,platform independent
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: mi-mx
|
|
38
|
+
Requires-Dist: mi-pyral
|
|
39
|
+
Requires-Dist: PyYAML
|
|
40
|
+
Provides-Extra: build
|
|
41
|
+
Requires-Dist: build; extra == "build"
|
|
42
|
+
Requires-Dist: twine; extra == "build"
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: bump2version; extra == "dev"
|
|
45
|
+
Requires-Dist: pytest; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# model-debugger
|
|
49
|
+
A modeling language independent model debugger
|
mi_mdb-0.1.0/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mi-mdb"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shlaer-Mellor Executable UML Model Debugger"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "Leon Starr", email = "leon_starr@modelint.com" }]
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
classifiers = [
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
]
|
|
17
|
+
keywords = ["shlaer-mellor", "metamodel", "executable uml", "mbse", "xuml", "xtuml", "model debugger", "platform independent"]
|
|
18
|
+
dependencies = ["mi-mx", "mi-pyral", "PyYAML"]
|
|
19
|
+
requires-python = ">=3.11"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
build = ["build", "twine"]
|
|
23
|
+
dev = ["bump2version", "pytest"]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
mdb = "mdb.__main__:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
repository = "https://github.com/modelint/model-debugger"
|
|
30
|
+
documentation = "https://github.com/modelint/model-debugger/wiki"
|
mi_mdb-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.1.0"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint Model Debugger
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# System
|
|
6
|
+
import logging
|
|
7
|
+
import logging.config
|
|
8
|
+
import sys
|
|
9
|
+
import argparse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import atexit
|
|
12
|
+
|
|
13
|
+
# MDB
|
|
14
|
+
from mdb.session import Session
|
|
15
|
+
from mdb import version
|
|
16
|
+
|
|
17
|
+
_logpath = Path("mdb.log")
|
|
18
|
+
_progname = 'Blueprint Model Debugger'
|
|
19
|
+
|
|
20
|
+
def clean_up():
|
|
21
|
+
"""Normal and exception exit activities"""
|
|
22
|
+
_logpath.unlink(missing_ok=True)
|
|
23
|
+
|
|
24
|
+
def get_logger():
|
|
25
|
+
"""Initiate the logger"""
|
|
26
|
+
log_conf_path = Path(__file__).parent / 'log.conf' # Logging configuration is in this file
|
|
27
|
+
logging.config.fileConfig(fname=log_conf_path, disable_existing_loggers=False)
|
|
28
|
+
return logging.getLogger(__name__) # Create a logger for this module
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Configure the expected parameters and actions for the argparse module
|
|
32
|
+
def parse(cl_input):
|
|
33
|
+
parser = argparse.ArgumentParser(description=_progname)
|
|
34
|
+
parser.add_argument('-s', '--system', action='store',
|
|
35
|
+
help='Name of the metamodel TclRAL database *.ral file populated with one or more domains')
|
|
36
|
+
parser.add_argument('-p', '--playground', action='store',
|
|
37
|
+
help='Name of the context directory specifying the initialized domain dbs and a *.sip file')
|
|
38
|
+
parser.add_argument('-x', '--scenario', action='store',
|
|
39
|
+
help='Name of the scenario *.yaml file to run against the populated system')
|
|
40
|
+
parser.add_argument('-L', '--log', action='store_true',
|
|
41
|
+
help='Generate a diagnostic log file')
|
|
42
|
+
parser.add_argument('-v', '--verbose', action='store_true',
|
|
43
|
+
help='Verbose messages')
|
|
44
|
+
parser.add_argument('-V', '--version', action='store_true',
|
|
45
|
+
help='Print the current version of the model execution app')
|
|
46
|
+
return parser.parse_args(cl_input)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
# Start logging
|
|
51
|
+
logger = get_logger()
|
|
52
|
+
msg = f'{_progname} version: {version}\n'
|
|
53
|
+
logger.info(msg)
|
|
54
|
+
|
|
55
|
+
# Parse the command line args
|
|
56
|
+
args = parse(sys.argv[1:])
|
|
57
|
+
|
|
58
|
+
if args.version:
|
|
59
|
+
# Just print the version and quit
|
|
60
|
+
print(f'{_progname} version: {version}')
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
if not args.log:
|
|
64
|
+
# If no log file is requested, remove the log file before termination
|
|
65
|
+
atexit.register(clean_up)
|
|
66
|
+
|
|
67
|
+
print(f"\n{msg}")
|
|
68
|
+
if args.verbose:
|
|
69
|
+
print("\nVerbose mode set\n")
|
|
70
|
+
|
|
71
|
+
# All pathnames are optional since the user can specify them in the debug session
|
|
72
|
+
session = Session() # Create the singleton instance
|
|
73
|
+
session.initialize(
|
|
74
|
+
system_path=Path(args.system) if args.system else None,
|
|
75
|
+
playground_name=args.playground if args.playground else None,
|
|
76
|
+
scenario_name=args.scenario if args.scenario else None,
|
|
77
|
+
verbose=args.verbose)
|
|
78
|
+
|
|
79
|
+
logger.info("No problemo") # We didn't die on an exception, basically
|
|
80
|
+
if args.verbose:
|
|
81
|
+
print("\nNo problemo")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
""" system.py -- System """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from mdb.ui_types import *
|
|
9
|
+
|
|
10
|
+
class SystemState(Enum):
|
|
11
|
+
LOADED = "LOADED"
|
|
12
|
+
NOT_LOADED = "NOT_LOADED"
|
|
13
|
+
RUNNING = "RUNNING"
|
|
14
|
+
SUSPENDED = "SUSPENDED"
|
|
15
|
+
TERMINATED = "TERMINATED"
|
|
16
|
+
|
|
17
|
+
def validate_path(path: Path) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Validates that a Path exists and is accessible.
|
|
20
|
+
Returns True if valid, prints error to console if not and returns False
|
|
21
|
+
"""
|
|
22
|
+
if not path.is_absolute():
|
|
23
|
+
print(f"Path is not absolute: {path}")
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
if not path.exists():
|
|
27
|
+
print(f"Path does not exist: {path}")
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
if not path.is_file() and not path.is_dir():
|
|
31
|
+
print(f"Path is neither a file nor a directory: {path}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class System:
|
|
38
|
+
"""
|
|
39
|
+
The System being tested
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, system_path: Optional[Path] = None, context_dir: Optional[Path] = None):
|
|
44
|
+
|
|
45
|
+
self.path = system_path
|
|
46
|
+
self.name = system_path.name if system_path else None
|
|
47
|
+
self.mmdb_fname = None
|
|
48
|
+
self.playground = None
|
|
49
|
+
self.state = SystemState.NOT_LOADED
|
|
50
|
+
|
|
51
|
+
if self.path:
|
|
52
|
+
self.load_models()
|
|
53
|
+
|
|
54
|
+
def load_models(self):
|
|
55
|
+
"""
|
|
56
|
+
Tell the MX to load the system
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
print(f"Loading models for: {self.name}{STATUS}")
|
|
62
|
+
self.state = SystemState.LOADED
|
|
63
|
+
model_path = self.path / 'models'
|
|
64
|
+
ral_files = list(model_path.glob("*.ral"))
|
|
65
|
+
#
|
|
66
|
+
if len(ral_files) == 0:
|
|
67
|
+
print(f"Error: No .ral file found in '{self.path}'")
|
|
68
|
+
return None
|
|
69
|
+
elif len(ral_files) > 1:
|
|
70
|
+
print(f"Error: Multiple .ral files found in '{self.path}': {[f.name for f in ral_files]}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
self.mmdb_fname = Path(ral_files[0])
|
|
74
|
+
|
|
75
|
+
print(f"Model file is: {self.mmdb_fname.name}")
|
|
76
|
+
|
|
77
|
+
def set_playground(self, playground_name: str):
|
|
78
|
+
"""
|
|
79
|
+
Validate path and, if valid, set it
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
playground_name:
|
|
83
|
+
"""
|
|
84
|
+
if validate_path(self.path / 'playgrounds' / playground_name ):
|
|
85
|
+
self.playground = playground_name
|
|
86
|
+
|
|
87
|
+
def set_path(self, system_path: Path):
|
|
88
|
+
"""
|
|
89
|
+
Validate path and, if valid, set it
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
system_path:
|
|
93
|
+
"""
|
|
94
|
+
# TODO: Ensure that System is in a compatible state such as NOT_LOADED
|
|
95
|
+
if validate_path(system_path):
|
|
96
|
+
self.path = system_path
|
|
97
|
+
# Reset local sub-paths
|
|
98
|
+
# TODO: changing the path should result in a new instance of System
|
|
99
|
+
self.playground = None
|
|
100
|
+
self.mmdb_fname = None
|
|
101
|
+
|
|
102
|
+
def show_playgrounds(self):
|
|
103
|
+
play_path = self.path / 'playgrounds'
|
|
104
|
+
pg_dirs = [f for f in play_path.iterdir() if f.is_dir()]
|
|
105
|
+
for d in pg_dirs:
|
|
106
|
+
print(f" {d.name}")
|
|
107
|
+
|
|
108
|
+
def show_active_playground(self):
|
|
109
|
+
if self.playground:
|
|
110
|
+
print(f" Active playground: {self.playground}")
|
|
111
|
+
return
|
|
112
|
+
print(" No active playground")
|
|
113
|
+
|
|
114
|
+
def show_path(self):
|
|
115
|
+
if self.path:
|
|
116
|
+
print(f"System path is: [{self.path}]")
|
|
117
|
+
else:
|
|
118
|
+
print(f"System path not set")
|
|
119
|
+
|
|
120
|
+
def show_models(self, classes: list[str]):
|
|
121
|
+
"""
|
|
122
|
+
Print out the loaded models
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
classes: A list of requested metamodel classes to view. If list is empty, entire mmdb is displayed
|
|
126
|
+
"""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
def load_context(self):
|
|
130
|
+
"""
|
|
131
|
+
Tell the MX to load the context
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
pass
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[loggers]
|
|
2
|
+
keys=root,MDBLogger,PyralLogger
|
|
3
|
+
|
|
4
|
+
[handlers]
|
|
5
|
+
keys=fileHandler, consoleHandler, consoleHandlerUser
|
|
6
|
+
|
|
7
|
+
[formatters]
|
|
8
|
+
keys=MDBFormatter, MDBFormatterUser
|
|
9
|
+
|
|
10
|
+
[logger_root]
|
|
11
|
+
level=DEBUG
|
|
12
|
+
handlers=fileHandler, consoleHandlerUser
|
|
13
|
+
|
|
14
|
+
[logger_PyralLogger]
|
|
15
|
+
level=WARNING
|
|
16
|
+
handlers=fileHandler
|
|
17
|
+
qualname=pyral
|
|
18
|
+
propagate=0
|
|
19
|
+
|
|
20
|
+
[logger_MDBLogger]
|
|
21
|
+
level=DEBUG
|
|
22
|
+
handlers=fileHandler, consoleHandlerUser
|
|
23
|
+
qualname=MDBLogger
|
|
24
|
+
propagate=0
|
|
25
|
+
|
|
26
|
+
[handler_fileHandler]
|
|
27
|
+
class=FileHandler
|
|
28
|
+
level=DEBUG
|
|
29
|
+
formatter=MDBFormatter
|
|
30
|
+
args=('mdb.log', 'w')
|
|
31
|
+
|
|
32
|
+
[handler_consoleHandlerUser]
|
|
33
|
+
class=StreamHandler
|
|
34
|
+
level=WARNING
|
|
35
|
+
formatter=MDBFormatterUser
|
|
36
|
+
args=(sys.stderr,)
|
|
37
|
+
|
|
38
|
+
[handler_consoleHandler]
|
|
39
|
+
class=StreamHandler
|
|
40
|
+
level=WARNING
|
|
41
|
+
formatter=MDBFormatter
|
|
42
|
+
args=(sys.stderr,)
|
|
43
|
+
|
|
44
|
+
[formatter_MDBFormatter]
|
|
45
|
+
format= MDB: %(name)s - %(levelname)s - %(message)s
|
|
46
|
+
|
|
47
|
+
[formatter_MDBFormatterUser]
|
|
48
|
+
format=MDB: %(levelname)s - %(message)s
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""scenario.py -- Scenario class"""
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
# Model Integration
|
|
8
|
+
from mx.mxtypes import *
|
|
9
|
+
|
|
10
|
+
class Scenario:
|
|
11
|
+
|
|
12
|
+
def __init__(self, sfile: Path):
|
|
13
|
+
"""
|
|
14
|
+
Args:
|
|
15
|
+
file: Path to a scenario yaml file
|
|
16
|
+
"""
|
|
17
|
+
# Load the yaml file
|
|
18
|
+
with open(sfile, "r") as file:
|
|
19
|
+
sdata = yaml.safe_load(file) # Load YAML content safely
|
|
20
|
+
|
|
21
|
+
# Unpack the Actors
|
|
22
|
+
actors = {}
|
|
23
|
+
internal_actor_parse = sdata['Actors']['internal']
|
|
24
|
+
for domain, instances in internal_actor_parse.items():
|
|
25
|
+
for name, address in instances.items():
|
|
26
|
+
instance_id = {attr: value for attr, value in address['instance'].items()}
|
|
27
|
+
actors[f"{domain}:{name}"] = InstanceAddress(domain=domain, class_name=address['class'],
|
|
28
|
+
instance_id=instance_id)
|
|
29
|
+
|
|
30
|
+
external_actor_parse = sdata['Actors']['external']
|
|
31
|
+
for ea in external_actor_parse:
|
|
32
|
+
actors[ea] = ExternalAddress(domain=ea)
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# Unpack the interactions
|
|
36
|
+
self.interactions = []
|
|
37
|
+
for i in sdata['Interactions']:
|
|
38
|
+
ituple = Interaction(
|
|
39
|
+
description=i['description'],
|
|
40
|
+
direction=Direction(i['direction']),
|
|
41
|
+
action=ActionType(i['action']),
|
|
42
|
+
name=i['name'],
|
|
43
|
+
source=actors[i['source']],
|
|
44
|
+
target=actors[i['target']],
|
|
45
|
+
parameters=i.get('parameters', {})
|
|
46
|
+
)
|
|
47
|
+
self.interactions.append(ituple)
|
|
48
|
+
pass
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
""" session.py -- Model Debugger Session """
|
|
2
|
+
|
|
3
|
+
# System
|
|
4
|
+
import shlex
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
# Model Integration
|
|
12
|
+
from mx.system import System
|
|
13
|
+
from mx.mxtypes import *
|
|
14
|
+
|
|
15
|
+
# MDB
|
|
16
|
+
from mdb.ui_types import *
|
|
17
|
+
from mdb.scenario import Scenario
|
|
18
|
+
|
|
19
|
+
STEP_PROMPT = '>: '
|
|
20
|
+
|
|
21
|
+
_logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def shortcut_index(s: str) -> int | None:
|
|
25
|
+
"""
|
|
26
|
+
Convert value to an integer shortcut if it is a 1-2 char integer with no leading zeros
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
s - user string input
|
|
30
|
+
Returns:
|
|
31
|
+
An integer shortcut if detected, otherwise None
|
|
32
|
+
"""
|
|
33
|
+
return int(s) if re.fullmatch(r'[1-9]\d?', s) else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Session:
|
|
37
|
+
"""
|
|
38
|
+
This class represents the debugger session. It follows the singleton pattern to ensure only one exists.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
system: MX system object we are debugging
|
|
42
|
+
playground_name: Name of the active playground (same as file name in system playgrounds dir)
|
|
43
|
+
scenario_name: Name of the active scenario (same as file name minus any extension in the
|
|
44
|
+
playground scenarios dir)
|
|
45
|
+
playground_scenarios: All scenario files defined for the active playground minus any file extension
|
|
46
|
+
active_scenario: Name of the scenario we are currently running
|
|
47
|
+
verbose: Whether verbose output is enabled
|
|
48
|
+
stepping: Whether stepping mode is enabled (pauses between interactions)
|
|
49
|
+
descriptions: Whether interaction descriptions are printed during scenario execution
|
|
50
|
+
"""
|
|
51
|
+
_instance = None
|
|
52
|
+
|
|
53
|
+
def __new__(cls):
|
|
54
|
+
if cls._instance is None:
|
|
55
|
+
cls._instance = super(Session, cls).__new__(cls)
|
|
56
|
+
return cls._instance
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
# Avoid singleton reinitialization if already initialized
|
|
60
|
+
if getattr(self, "_initialized", False):
|
|
61
|
+
return
|
|
62
|
+
self._initialized = True
|
|
63
|
+
|
|
64
|
+
self.system = None
|
|
65
|
+
self.playground_name = None
|
|
66
|
+
self.scenario_name = None
|
|
67
|
+
self.playground_scenarios: list[str] = []
|
|
68
|
+
self.active_scenario = None
|
|
69
|
+
self.verbose = False
|
|
70
|
+
self.stepping = False # By default we assume that the user wants to run the scenario without stepping
|
|
71
|
+
self.descriptions = False
|
|
72
|
+
|
|
73
|
+
def initialize(self, verbose: bool,
|
|
74
|
+
system_path: Path = None,
|
|
75
|
+
playground_name: str = None,
|
|
76
|
+
scenario_name: str = None,
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
We can initialize a session without any system and then let the user initialize it interactively
|
|
80
|
+
or, if a system path is provided, we can initialize it right away. The same goes for the playground and
|
|
81
|
+
scenario names.
|
|
82
|
+
|
|
83
|
+
After whatever degree of initialization is completed, we start the user command input loop
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
verbose: Verbose mode for stdout (console) messaging/status
|
|
87
|
+
system_path: An absolute path to the system directory where the entire system is defined
|
|
88
|
+
playground_name: Name of a playground corresponding ot the name of a file in the system's playground dir
|
|
89
|
+
scenario_name: Name of a scenario file (yaml, for now) that the mdb loads locally, but is found in the
|
|
90
|
+
system's scenarios directory
|
|
91
|
+
"""
|
|
92
|
+
print("Initializing session from command line arguments...")
|
|
93
|
+
if system_path:
|
|
94
|
+
self.system = System() # Singleton initialization
|
|
95
|
+
if system_path:
|
|
96
|
+
self.system.initialize(system_path=system_path, verbose=verbose)
|
|
97
|
+
else:
|
|
98
|
+
print("System not yet initialized. Use: set system <path> to initialize.")
|
|
99
|
+
self.scenario_name = scenario_name
|
|
100
|
+
self.playground_name = playground_name
|
|
101
|
+
self.verbose = verbose
|
|
102
|
+
|
|
103
|
+
# Initialize these if they were supplied on the command line
|
|
104
|
+
if playground_name:
|
|
105
|
+
self.set_playground(playground_name)
|
|
106
|
+
if scenario_name:
|
|
107
|
+
self.set_scenario(scenario_name)
|
|
108
|
+
|
|
109
|
+
self.run()
|
|
110
|
+
|
|
111
|
+
def run(self):
|
|
112
|
+
"""
|
|
113
|
+
This is the user input command loop. It just runs until the user exits.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
print("\nType 'help' for available commands, 'quit' or 'exit' to exit\n")
|
|
117
|
+
|
|
118
|
+
while True: # Breaks out when the user enters the quit/exit command
|
|
119
|
+
try:
|
|
120
|
+
raw = input(CMD_PROMPT).strip()
|
|
121
|
+
except (EOFError, KeyboardInterrupt):
|
|
122
|
+
print()
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if not raw:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
parts = shlex.split(raw)
|
|
130
|
+
except ValueError as e:
|
|
131
|
+
print(f"Parse error: {e}")
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
command, *args = parts
|
|
135
|
+
|
|
136
|
+
match command.lower():
|
|
137
|
+
case "quit" | "exit":
|
|
138
|
+
break
|
|
139
|
+
case "help":
|
|
140
|
+
self.cmd_help(args)
|
|
141
|
+
case "show":
|
|
142
|
+
self.cmd_show(args)
|
|
143
|
+
case "list":
|
|
144
|
+
self.cmd_list(args)
|
|
145
|
+
case "set":
|
|
146
|
+
self.cmd_set(args)
|
|
147
|
+
case "execute" | "x" | "exec":
|
|
148
|
+
self.execute_scenario()
|
|
149
|
+
case _:
|
|
150
|
+
print(f"Unknown command: '{command}'. Type 'help' for available commands.")
|
|
151
|
+
|
|
152
|
+
def show_stepping_status(self):
|
|
153
|
+
if self.stepping:
|
|
154
|
+
print("Stepping mode set")
|
|
155
|
+
else:
|
|
156
|
+
print("Run to completion mode set")
|
|
157
|
+
|
|
158
|
+
def show_descriptions_status(self):
|
|
159
|
+
if self.descriptions:
|
|
160
|
+
print("Descriptions mode set")
|
|
161
|
+
else:
|
|
162
|
+
print("Descriptions mode not set")
|
|
163
|
+
|
|
164
|
+
def show_states(self) -> None:
|
|
165
|
+
if not self.system:
|
|
166
|
+
print("No system loaded.")
|
|
167
|
+
return
|
|
168
|
+
for domain_name, domain in self.system.domains.items():
|
|
169
|
+
current_states = domain.get_current_states()
|
|
170
|
+
if not current_states:
|
|
171
|
+
continue
|
|
172
|
+
print(f"\n{domain_name} current states:")
|
|
173
|
+
print("—")
|
|
174
|
+
for sm in current_states:
|
|
175
|
+
if sm.instance:
|
|
176
|
+
inst_str = '<' + '-'.join(f"{k}:{v}" for k, v in sm.instance.items()) + '>'
|
|
177
|
+
else:
|
|
178
|
+
inst_str = ""
|
|
179
|
+
print(f"{sm.state_model} {inst_str} [{sm.state}]")
|
|
180
|
+
print("—")
|
|
181
|
+
|
|
182
|
+
def cmd_show(self, args: list[str]) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Display requested item on console
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
args: What to display
|
|
188
|
+
"""
|
|
189
|
+
if len(args) < 1:
|
|
190
|
+
print("Usage: set <item>")
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
item = args[0]
|
|
194
|
+
match item:
|
|
195
|
+
case 'scenario':
|
|
196
|
+
print("Not implemented yet.")
|
|
197
|
+
case 'path':
|
|
198
|
+
self.system.show_path()
|
|
199
|
+
case 'playground' | 'pg':
|
|
200
|
+
print(f"Active playground: {self.system.playground}")
|
|
201
|
+
case 'playgrounds' | 'pgs':
|
|
202
|
+
self.show_playgrounds()
|
|
203
|
+
case 'active':
|
|
204
|
+
if args[1] == 'playground':
|
|
205
|
+
self.system.show_active_playground()
|
|
206
|
+
else:
|
|
207
|
+
print(f"Unknown active element: {args[1]}")
|
|
208
|
+
case 'scenarios':
|
|
209
|
+
self.show_scenarios()
|
|
210
|
+
case 'step':
|
|
211
|
+
self.show_stepping_status()
|
|
212
|
+
case 'descriptions' | 'desc':
|
|
213
|
+
self.show_descriptions_status()
|
|
214
|
+
case 'states':
|
|
215
|
+
self.show_states()
|
|
216
|
+
case _:
|
|
217
|
+
print(f"Unknown item: {item}")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
pass
|
|
221
|
+
# TODO: Have MX load the ral file and initialize the system
|
|
222
|
+
|
|
223
|
+
def cmd_help(self, args: list[str]) -> None:
|
|
224
|
+
print("Commands:")
|
|
225
|
+
print(" show <item> - Display item on console (ex: show path)")
|
|
226
|
+
print(" show states - Display current state of all state models")
|
|
227
|
+
print(" set <variable> <value> - Set a variable (ex: set path ~/my/path)")
|
|
228
|
+
print(" set step - Toggle stepping mode (pause between interactions)")
|
|
229
|
+
print(" set desc - Toggle descriptions mode (print interaction descriptions)")
|
|
230
|
+
print(" execute / exec / x - Execute the active scenario")
|
|
231
|
+
print(" quit / exit - Exit the debugger")
|
|
232
|
+
|
|
233
|
+
def cmd_set(self, args: list[str]) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Set the value of some variable
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
args:
|
|
239
|
+
"""
|
|
240
|
+
if len(args) > 2:
|
|
241
|
+
print("Usage: set <variable> [<value>]")
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
variable, value = args[0], args[1] if len(args) > 1 else None
|
|
245
|
+
vset = False
|
|
246
|
+
match variable:
|
|
247
|
+
case 'path':
|
|
248
|
+
self.system.set_path(system_path=Path(value))
|
|
249
|
+
case 'playground' | 'pg':
|
|
250
|
+
self.set_playground(value)
|
|
251
|
+
case 'scenario':
|
|
252
|
+
self.set_scenario(value)
|
|
253
|
+
case 'step':
|
|
254
|
+
# Toggle the setting
|
|
255
|
+
self.stepping = not self.stepping
|
|
256
|
+
self.show_stepping_status()
|
|
257
|
+
case 'descriptions' | 'desc':
|
|
258
|
+
# Toggle the setting
|
|
259
|
+
self.descriptions = not self.descriptions
|
|
260
|
+
self.show_descriptions_status()
|
|
261
|
+
case _:
|
|
262
|
+
vset = False
|
|
263
|
+
print(f"Setting {variable} not defined")
|
|
264
|
+
|
|
265
|
+
if vset:
|
|
266
|
+
print(f"{variable} = {value}")
|
|
267
|
+
|
|
268
|
+
def cmd_list(self, args: list[str]) -> None:
|
|
269
|
+
if not args:
|
|
270
|
+
print("Usage: list <target>")
|
|
271
|
+
return
|
|
272
|
+
target = args[0]
|
|
273
|
+
print(f" [stub] list {target}")
|
|
274
|
+
|
|
275
|
+
def show_playgrounds(self):
|
|
276
|
+
"""Display all playgrounds defined in the system directory"""
|
|
277
|
+
system_playgrounds = self.system.playgrounds
|
|
278
|
+
if system_playgrounds is not None:
|
|
279
|
+
print("Available playgrounds:")
|
|
280
|
+
for n, p in enumerate(self.system.available_playgrounds):
|
|
281
|
+
print(f" [{n + 1}] - {p}")
|
|
282
|
+
|
|
283
|
+
def set_playground(self, value: str) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Verify that the supplied value corresponds to a valid playground.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
value: A full string name of a playground directory or shortcut 1-2 integer index
|
|
289
|
+
"""
|
|
290
|
+
i = shortcut_index(value) # i is between 1 and 99 or None
|
|
291
|
+
if i is not None:
|
|
292
|
+
if i > len(self.system.available_playgrounds):
|
|
293
|
+
print(f"Undefined playground shortcut: [{value[1:]}]")
|
|
294
|
+
return
|
|
295
|
+
pg_name = self.system.playgrounds[i - 1] # User counts items starting from 1
|
|
296
|
+
else:
|
|
297
|
+
pg_name = value
|
|
298
|
+
if pg_name not in self.system.playgrounds:
|
|
299
|
+
print(f"Unknown playground: {pg_name}")
|
|
300
|
+
return
|
|
301
|
+
print(f"Selected playground: {pg_name}")
|
|
302
|
+
|
|
303
|
+
self.system.load_domains(playground=pg_name)
|
|
304
|
+
# And find all available scenarios for that playground
|
|
305
|
+
self.show_scenarios()
|
|
306
|
+
|
|
307
|
+
def set_scenario(self, value: str):
|
|
308
|
+
i = shortcut_index(value) # i is between 1 and 99 or None
|
|
309
|
+
if i is not None:
|
|
310
|
+
if not self.playground_scenarios:
|
|
311
|
+
print(f"Unknown scenario: {value}")
|
|
312
|
+
self.show_scenarios()
|
|
313
|
+
return
|
|
314
|
+
if i > len(self.playground_scenarios):
|
|
315
|
+
print(f"Undefined scenario shortcut: [{value[1:]}]")
|
|
316
|
+
return
|
|
317
|
+
scenario_name = self.playground_scenarios[i - 1] # User counts items starting from 1
|
|
318
|
+
else:
|
|
319
|
+
scenario_name = value
|
|
320
|
+
if scenario_name not in self.playground_scenarios:
|
|
321
|
+
print(f"Unknown scenario: {scenario_name}")
|
|
322
|
+
return
|
|
323
|
+
print(f"Selected scenario: {scenario_name}")
|
|
324
|
+
|
|
325
|
+
sfile = self.system.playground / 'scenarios' / (scenario_name + ".yaml")
|
|
326
|
+
self.active_scenario = Scenario(sfile)
|
|
327
|
+
|
|
328
|
+
def show_scenarios(self) -> None:
|
|
329
|
+
"""
|
|
330
|
+
Display all scenarios defined in the active playground directory
|
|
331
|
+
with convenient integer shortcuts
|
|
332
|
+
"""
|
|
333
|
+
scenario_path = self.system.playground / 'scenarios'
|
|
334
|
+
scenario_paths = list(scenario_path.glob("*.yaml"))
|
|
335
|
+
self.playground_scenarios = [f.stem for f in scenario_paths]
|
|
336
|
+
if self.playground_scenarios:
|
|
337
|
+
print("Active playground scenarios:")
|
|
338
|
+
for n, p in enumerate(self.playground_scenarios):
|
|
339
|
+
print(f" [{n + 1}] - {p}")
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def format_interaction(i: Interaction):
|
|
343
|
+
if i.action == ActionType.SIGNAL_INSTANCE:
|
|
344
|
+
inst_str = '<' + '-'.join([str(v) for v in i.target.instance_id.values()]) + '>'
|
|
345
|
+
formatted_i = f"{i.source.domain} >|| {i.target.domain} : {i.name} -> {i.target.class_name} {inst_str}"
|
|
346
|
+
else:
|
|
347
|
+
formatted_i = "Unimplemented Acton Type"
|
|
348
|
+
print(formatted_i)
|
|
349
|
+
|
|
350
|
+
@staticmethod
|
|
351
|
+
def format_announcements(announcement_tuples: list[Announcement]):
|
|
352
|
+
for a in announcement_tuples:
|
|
353
|
+
if isinstance(a, ExternalEvent_Announcement):
|
|
354
|
+
if a.inst:
|
|
355
|
+
inst_str = '<' + '-'.join([str(v) for v in a.inst.values()]) + '>'
|
|
356
|
+
else:
|
|
357
|
+
inst_str = ""
|
|
358
|
+
pstrings = [f"{n}={v[0]}" for n,v in a.params.items()]
|
|
359
|
+
param_str = ', '.join(pstrings)
|
|
360
|
+
formatted_a = f"{a.domain} >|| {a.ee} : {a.source}{inst_str} {a.event}( {param_str} )"
|
|
361
|
+
print(f" {formatted_a}")
|
|
362
|
+
|
|
363
|
+
def execute_scenario(self) -> None:
|
|
364
|
+
"""
|
|
365
|
+
Process each interaction of the scenario until it is complete
|
|
366
|
+
"""
|
|
367
|
+
print(f"Running scenario: {self.scenario_name}...\n")
|
|
368
|
+
# Set external events to trigger an announcement and return control after enclosing activity completes
|
|
369
|
+
System.set_announce_triggers(['external signal'])
|
|
370
|
+
|
|
371
|
+
interactions = iter(self.active_scenario.interactions)
|
|
372
|
+
i = next(interactions, None)
|
|
373
|
+
|
|
374
|
+
while i is not None:
|
|
375
|
+
if self.descriptions:
|
|
376
|
+
print(f" ** {i.description} ** ")
|
|
377
|
+
# Process the current interaction
|
|
378
|
+
if i.direction == Direction.STIMULUS:
|
|
379
|
+
Session.format_interaction(i) # Print out the stimulus injection info
|
|
380
|
+
self.system.inject(stimulus=i) # Inject the stimulus, transferring control to MX
|
|
381
|
+
self.format_announcements(self.system.announcements) # Print out any triggered announcments
|
|
382
|
+
|
|
383
|
+
# TODO: Think about how to correlate announcements and responses
|
|
384
|
+
# The triggered announcments should correspond to the expedted response interactions
|
|
385
|
+
# but at this point, we doin't attempt to match them up. This may be in the realm of a testing
|
|
386
|
+
# package, but we keep them in the scenario yaml for now.
|
|
387
|
+
else: # Response
|
|
388
|
+
self.system.go() # No stimulus to inject, so just pass control back to MX
|
|
389
|
+
self.format_announcements(self.system.announcements) # Print out any triggered announcments
|
|
390
|
+
|
|
391
|
+
if not self.stepping:
|
|
392
|
+
i = next(interactions, None)
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
# Inner pause loop: let user inspect results before stepping forward
|
|
396
|
+
while True:
|
|
397
|
+
try:
|
|
398
|
+
raw = input(STEP_PROMPT).strip().lower()
|
|
399
|
+
except (EOFError, KeyboardInterrupt):
|
|
400
|
+
print("\nScenario aborted.")
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
match raw:
|
|
404
|
+
case "" | "n" | "next" | "s" | "step":
|
|
405
|
+
i = next(interactions, None) # Advance to the next interaction
|
|
406
|
+
break
|
|
407
|
+
case "r" | "run":
|
|
408
|
+
self.stepping = False # Run to completion
|
|
409
|
+
i = next(interactions, None)
|
|
410
|
+
break
|
|
411
|
+
case "q" | "quit" | "abort":
|
|
412
|
+
print("Scenario aborted.")
|
|
413
|
+
return
|
|
414
|
+
case "show states":
|
|
415
|
+
self.show_states()
|
|
416
|
+
case "h" | "help" | "?":
|
|
417
|
+
print(" [enter] / n / next / s / step - Advance to next interaction")
|
|
418
|
+
print(" show states - Display current state of all state models")
|
|
419
|
+
print(" q / quit / abort - Abort scenario")
|
|
420
|
+
case _:
|
|
421
|
+
print(f"Unknown step command: '{raw}'. Type 'h' for help.")
|
|
422
|
+
|
|
423
|
+
print("\nScenario complete.")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mi-mdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shlaer-Mellor Executable UML Model Debugger
|
|
5
|
+
Author-email: Leon Starr <leon_starr@modelint.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Leon Starr
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: repository, https://github.com/modelint/model-debugger
|
|
29
|
+
Project-URL: documentation, https://github.com/modelint/model-debugger/wiki
|
|
30
|
+
Keywords: shlaer-mellor,metamodel,executable uml,mbse,xuml,xtuml,model debugger,platform independent
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: mi-mx
|
|
38
|
+
Requires-Dist: mi-pyral
|
|
39
|
+
Requires-Dist: PyYAML
|
|
40
|
+
Provides-Extra: build
|
|
41
|
+
Requires-Dist: build; extra == "build"
|
|
42
|
+
Requires-Dist: twine; extra == "build"
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: bump2version; extra == "dev"
|
|
45
|
+
Requires-Dist: pytest; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# model-debugger
|
|
49
|
+
A modeling language independent model debugger
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/mdb/__init__.py
|
|
6
|
+
src/mdb/__main__.py
|
|
7
|
+
src/mdb/_dep_system.py
|
|
8
|
+
src/mdb/log.conf
|
|
9
|
+
src/mdb/scenario.py
|
|
10
|
+
src/mdb/session.py
|
|
11
|
+
src/mdb/ui_types.py
|
|
12
|
+
src/mi_mdb.egg-info/PKG-INFO
|
|
13
|
+
src/mi_mdb.egg-info/SOURCES.txt
|
|
14
|
+
src/mi_mdb.egg-info/dependency_links.txt
|
|
15
|
+
src/mi_mdb.egg-info/entry_points.txt
|
|
16
|
+
src/mi_mdb.egg-info/requires.txt
|
|
17
|
+
src/mi_mdb.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mdb
|