speclogician 0.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speclogician/__init__.py +0 -0
- speclogician/commands/__init__.py +15 -0
- speclogician/commands/cmd_ch.py +616 -0
- speclogician/commands/cmd_find.py +256 -0
- speclogician/commands/cmd_view.py +202 -0
- speclogician/commands/runner.py +149 -0
- speclogician/commands/utils.py +101 -0
- speclogician/data/__init__.py +0 -0
- speclogician/data/artifact.py +63 -0
- speclogician/data/container.py +402 -0
- speclogician/data/mapping.py +88 -0
- speclogician/data/refs.py +24 -0
- speclogician/data/traces.py +26 -0
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +278 -0
- speclogician/demos/loader.py +135 -0
- speclogician/demos/model.py +27 -0
- speclogician/demos/runner.py +51 -0
- speclogician/logic/__init__.py +11 -0
- speclogician/logic/api/__init__.py +29 -0
- speclogician/logic/api/client.py +606 -0
- speclogician/logic/api/decomp.py +67 -0
- speclogician/logic/api/scenario.py +102 -0
- speclogician/logic/api/traces.py +59 -0
- speclogician/logic/lib/__init__.py +19 -0
- speclogician/logic/lib/complement.py +107 -0
- speclogician/logic/lib/domain_model.py +59 -0
- speclogician/logic/lib/predicates.py +151 -0
- speclogician/logic/lib/scenarios.py +369 -0
- speclogician/logic/lib/traces.py +114 -0
- speclogician/logic/lib/transitions.py +104 -0
- speclogician/logic/main.py +246 -0
- speclogician/logic/strings.py +194 -0
- speclogician/logic/utils.py +135 -0
- speclogician/main.py +139 -0
- speclogician/modeling/__init__.py +31 -0
- speclogician/modeling/complement.py +104 -0
- speclogician/modeling/component.py +71 -0
- speclogician/modeling/conflict.py +26 -0
- speclogician/modeling/domain.py +349 -0
- speclogician/modeling/predicates.py +59 -0
- speclogician/modeling/scenario.py +162 -0
- speclogician/modeling/spec.py +306 -0
- speclogician/modeling/spec_stats.py +39 -0
- speclogician/presentation/__init__.py +0 -0
- speclogician/presentation/api.py +244 -0
- speclogician/presentation/builders/__init__.py +0 -0
- speclogician/presentation/builders/_links.py +44 -0
- speclogician/presentation/builders/container.py +53 -0
- speclogician/presentation/builders/data_artifact.py +42 -0
- speclogician/presentation/builders/domain.py +54 -0
- speclogician/presentation/builders/instances_list.py +38 -0
- speclogician/presentation/builders/predicate.py +51 -0
- speclogician/presentation/builders/recommendations.py +41 -0
- speclogician/presentation/builders/scenario.py +41 -0
- speclogician/presentation/builders/scenario_complement.py +82 -0
- speclogician/presentation/builders/smart_find.py +39 -0
- speclogician/presentation/builders/spec.py +39 -0
- speclogician/presentation/builders/state_diff.py +150 -0
- speclogician/presentation/builders/state_instance.py +42 -0
- speclogician/presentation/builders/state_instance_summary.py +84 -0
- speclogician/presentation/builders/trace.py +58 -0
- speclogician/presentation/ctx.py +38 -0
- speclogician/presentation/models/__init__.py +0 -0
- speclogician/presentation/models/container.py +44 -0
- speclogician/presentation/models/data_artifact.py +33 -0
- speclogician/presentation/models/domain.py +50 -0
- speclogician/presentation/models/instances_list.py +23 -0
- speclogician/presentation/models/predicate.py +60 -0
- speclogician/presentation/models/recommendations.py +34 -0
- speclogician/presentation/models/scenario.py +31 -0
- speclogician/presentation/models/scenario_complement.py +40 -0
- speclogician/presentation/models/smart_find.py +34 -0
- speclogician/presentation/models/spec.py +32 -0
- speclogician/presentation/models/state_diff.py +34 -0
- speclogician/presentation/models/state_instance.py +31 -0
- speclogician/presentation/models/state_instance_summary.py +102 -0
- speclogician/presentation/models/trace.py +42 -0
- speclogician/presentation/preview/__init__.py +13 -0
- speclogician/presentation/preview/cli.py +50 -0
- speclogician/presentation/preview/fixtures/__init__.py +205 -0
- speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
- speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
- speclogician/presentation/preview/fixtures/domain_model.py +162 -0
- speclogician/presentation/preview/fixtures/instances_list.py +162 -0
- speclogician/presentation/preview/fixtures/predicate.py +184 -0
- speclogician/presentation/preview/fixtures/scenario.py +84 -0
- speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
- speclogician/presentation/preview/fixtures/smart_find.py +140 -0
- speclogician/presentation/preview/fixtures/spec.py +95 -0
- speclogician/presentation/preview/fixtures/state_diff.py +158 -0
- speclogician/presentation/preview/fixtures/state_instance.py +128 -0
- speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
- speclogician/presentation/preview/fixtures/trace.py +206 -0
- speclogician/presentation/preview/registry.py +42 -0
- speclogician/presentation/renderers/__init__.py +24 -0
- speclogician/presentation/renderers/container.py +136 -0
- speclogician/presentation/renderers/data_artifact.py +144 -0
- speclogician/presentation/renderers/domain.py +123 -0
- speclogician/presentation/renderers/instances_list.py +120 -0
- speclogician/presentation/renderers/predicate.py +180 -0
- speclogician/presentation/renderers/recommendations.py +90 -0
- speclogician/presentation/renderers/scenario.py +94 -0
- speclogician/presentation/renderers/scenario_complement.py +59 -0
- speclogician/presentation/renderers/smart_find.py +307 -0
- speclogician/presentation/renderers/spec.py +105 -0
- speclogician/presentation/renderers/state_diff.py +102 -0
- speclogician/presentation/renderers/state_instance.py +82 -0
- speclogician/presentation/renderers/state_instance_summary.py +143 -0
- speclogician/presentation/renderers/trace.py +122 -0
- speclogician/py.typed +0 -0
- speclogician/shell/app.py +170 -0
- speclogician/shell/shell_ch.py +263 -0
- speclogician/shell/shell_view.py +153 -0
- speclogician/state/__init__.py +0 -0
- speclogician/state/change.py +428 -0
- speclogician/state/change_result.py +32 -0
- speclogician/state/diff.py +191 -0
- speclogician/state/inst.py +574 -0
- speclogician/state/recommendation.py +13 -0
- speclogician/state/recommender.py +577 -0
- speclogician/state/state.py +465 -0
- speclogician/state/state_stats.py +133 -0
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +257 -0
- speclogician/tui/app.tcss +160 -0
- speclogician/tui/demo.py +45 -0
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +454 -0
- speclogician/tui/splash_screen.py +51 -0
- speclogician/tui/stats_screen.py +125 -0
- speclogician/utils/__init__.py +78 -0
- speclogician/utils/load.py +166 -0
- speclogician/utils/prompt.md +325 -0
- speclogician/utils/testing.py +151 -0
- speclogician-0.0.0b1.dist-info/METADATA +116 -0
- speclogician-0.0.0b1.dist-info/RECORD +139 -0
- speclogician-0.0.0b1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/shell/shell_view.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from cmd2 import Cmd, with_argparser
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
14
|
+
from speclogician.presentation.api import (
|
|
15
|
+
present_state_instance,
|
|
16
|
+
present_spec,
|
|
17
|
+
present_domain_model,
|
|
18
|
+
present_artifact_container,
|
|
19
|
+
present_instances_list,
|
|
20
|
+
)
|
|
21
|
+
from speclogician.utils import console
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _mk_ctx(args) -> RenderCtx:
|
|
25
|
+
return RenderCtx(
|
|
26
|
+
compact=getattr(args, "compact", False),
|
|
27
|
+
show_stats_only=getattr(args, "stats_only", False),
|
|
28
|
+
show_code=not getattr(args, "no_code", False),
|
|
29
|
+
show_ids=getattr(args, "ids", False),
|
|
30
|
+
show_art_ids=getattr(args, "art_ids", False),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ViewShell(Cmd):
|
|
35
|
+
prompt = "sl:view> "
|
|
36
|
+
allow_cli_args = False
|
|
37
|
+
|
|
38
|
+
def __init__(self, parent_shell, *args, **kwargs):
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
self.parent = parent_shell
|
|
41
|
+
self.rich = console
|
|
42
|
+
|
|
43
|
+
# ---- shared flags for all view commands ----
|
|
44
|
+
def _add_common(self, p: argparse.ArgumentParser) -> None:
|
|
45
|
+
p.add_argument("--idx", type=int, default=0)
|
|
46
|
+
p.add_argument("--json", action="store_true")
|
|
47
|
+
p.add_argument("--compact", action="store_true")
|
|
48
|
+
p.add_argument("--stats-only", dest="stats_only", action="store_true")
|
|
49
|
+
p.add_argument("--no-code", action="store_true")
|
|
50
|
+
p.add_argument("--ids", action="store_true")
|
|
51
|
+
p.add_argument("--art-ids", dest="art_ids", action="store_true")
|
|
52
|
+
|
|
53
|
+
def _emit(self, x, *, json_only: bool) -> None:
|
|
54
|
+
if json_only:
|
|
55
|
+
self.rich.print_json(json.dumps(x, indent=2, default=str))
|
|
56
|
+
else:
|
|
57
|
+
self.rich.print(x)
|
|
58
|
+
|
|
59
|
+
def do_back(self, _arg) -> bool:
|
|
60
|
+
"""Return to main shell."""
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
do_exit = do_back
|
|
64
|
+
do_quit = do_back
|
|
65
|
+
|
|
66
|
+
# ----------------------
|
|
67
|
+
# view state/spec/domain/artifacts/instances
|
|
68
|
+
# ----------------------
|
|
69
|
+
|
|
70
|
+
state_p = argparse.ArgumentParser()
|
|
71
|
+
_ = state_p.add_argument
|
|
72
|
+
# common
|
|
73
|
+
# (cmd2 doesn't support easy composition; just call helper)
|
|
74
|
+
# We'll patch it in __init__? easiest: do it here:
|
|
75
|
+
# (but need self, so do it in method; use a helper below instead)
|
|
76
|
+
|
|
77
|
+
@with_argparser(argparse.ArgumentParser())
|
|
78
|
+
def do_state(self, args) -> None:
|
|
79
|
+
"""View full state instance."""
|
|
80
|
+
if self.parent.state is None:
|
|
81
|
+
self.perror("No state loaded.")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# build parser on the fly so we can reuse helper
|
|
85
|
+
p = argparse.ArgumentParser()
|
|
86
|
+
self._add_common(p)
|
|
87
|
+
a = p.parse_args(args.argv)
|
|
88
|
+
|
|
89
|
+
si = self.parent.state.instances[a.idx]
|
|
90
|
+
rctx = _mk_ctx(a)
|
|
91
|
+
out = present_state_instance(si, ctx=rctx, json_only=a.json)
|
|
92
|
+
self._emit(out, json_only=a.json)
|
|
93
|
+
|
|
94
|
+
@with_argparser(argparse.ArgumentParser())
|
|
95
|
+
def do_spec(self, args) -> None:
|
|
96
|
+
if self.parent.state is None:
|
|
97
|
+
self.perror("No state loaded.")
|
|
98
|
+
return
|
|
99
|
+
p = argparse.ArgumentParser()
|
|
100
|
+
self._add_common(p)
|
|
101
|
+
a = p.parse_args(args.argv)
|
|
102
|
+
|
|
103
|
+
si = self.parent.state.instances[a.idx]
|
|
104
|
+
rctx = _mk_ctx(a)
|
|
105
|
+
out = present_spec(si.spec, ctx=rctx, json_only=a.json)
|
|
106
|
+
self._emit(out, json_only=a.json)
|
|
107
|
+
|
|
108
|
+
@with_argparser(argparse.ArgumentParser())
|
|
109
|
+
def do_domain(self, args) -> None:
|
|
110
|
+
if self.parent.state is None:
|
|
111
|
+
self.perror("No state loaded.")
|
|
112
|
+
return
|
|
113
|
+
p = argparse.ArgumentParser()
|
|
114
|
+
self._add_common(p)
|
|
115
|
+
a = p.parse_args(args.argv)
|
|
116
|
+
|
|
117
|
+
si = self.parent.state.instances[a.idx]
|
|
118
|
+
rctx = _mk_ctx(a)
|
|
119
|
+
out = present_domain_model(si.spec.domain_model, ctx=rctx, json_only=a.json)
|
|
120
|
+
self._emit(out, json_only=a.json)
|
|
121
|
+
|
|
122
|
+
@with_argparser(argparse.ArgumentParser())
|
|
123
|
+
def do_artifacts(self, args) -> None:
|
|
124
|
+
if self.parent.state is None:
|
|
125
|
+
self.perror("No state loaded.")
|
|
126
|
+
return
|
|
127
|
+
p = argparse.ArgumentParser()
|
|
128
|
+
self._add_common(p)
|
|
129
|
+
a = p.parse_args(args.argv)
|
|
130
|
+
|
|
131
|
+
si = self.parent.state.instances[a.idx]
|
|
132
|
+
rctx = _mk_ctx(a)
|
|
133
|
+
out = present_artifact_container(si.art_container, ctx=rctx, json_only=a.json)
|
|
134
|
+
self._emit(out, json_only=a.json)
|
|
135
|
+
|
|
136
|
+
@with_argparser(argparse.ArgumentParser())
|
|
137
|
+
def do_instances(self, args) -> None:
|
|
138
|
+
if self.parent.state is None:
|
|
139
|
+
self.perror("No state loaded.")
|
|
140
|
+
return
|
|
141
|
+
p = argparse.ArgumentParser()
|
|
142
|
+
# instances view doesn’t need --idx but keep flags consistent:
|
|
143
|
+
p.add_argument("--json", action="store_true")
|
|
144
|
+
p.add_argument("--compact", action="store_true")
|
|
145
|
+
p.add_argument("--stats-only", dest="stats_only", action="store_true")
|
|
146
|
+
p.add_argument("--no-code", action="store_true")
|
|
147
|
+
p.add_argument("--ids", action="store_true")
|
|
148
|
+
p.add_argument("--art-ids", dest="art_ids", action="store_true")
|
|
149
|
+
a = p.parse_args(args.argv)
|
|
150
|
+
|
|
151
|
+
rctx = _mk_ctx(a)
|
|
152
|
+
out = present_instances_list(self.parent.state.instances, ctx=rctx, json_only=a.json)
|
|
153
|
+
self._emit(out, json_only=a.json)
|
|
File without changes
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/state/change.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
from rich.console import Group
|
|
12
|
+
from pydantic import BaseModel, Field, computed_field, model_validator
|
|
13
|
+
from typing import Literal, Optional
|
|
14
|
+
|
|
15
|
+
from ..modeling.domain import PredicateType
|
|
16
|
+
from ..modeling.scenario import ScenarioDelta
|
|
17
|
+
|
|
18
|
+
ProcessChangeStage = Literal["apply_change", "analyze_change", "calc_diff", "ok"]
|
|
19
|
+
|
|
20
|
+
class StateChange(BaseModel):
|
|
21
|
+
""" StateChange """
|
|
22
|
+
|
|
23
|
+
meta : str | None = None
|
|
24
|
+
|
|
25
|
+
@computed_field
|
|
26
|
+
@property
|
|
27
|
+
def kind(self) -> str:
|
|
28
|
+
return self.__class__.__name__
|
|
29
|
+
|
|
30
|
+
def short_str(self) -> str:
|
|
31
|
+
return f"[bold deep_blue_sky]{self.kind}[/bold deep_blue_sky]"
|
|
32
|
+
|
|
33
|
+
def __rich__(self) -> Panel:
|
|
34
|
+
rows: list[Text] = []
|
|
35
|
+
for k, v in self.model_dump(exclude_none=True, exclude={"kind"}).items():
|
|
36
|
+
rows.append(Text(f"{k} = {v!r}"))
|
|
37
|
+
return Panel(
|
|
38
|
+
Group(*rows),
|
|
39
|
+
title=f"[italic]{self.__class__.__name__}[/]",
|
|
40
|
+
title_align="left",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# ----------------------------
|
|
44
|
+
# Domain Base
|
|
45
|
+
# ----------------------------
|
|
46
|
+
|
|
47
|
+
class DomainModelBaseEdit(StateChange):
|
|
48
|
+
""" Edit the base component of domain model """
|
|
49
|
+
new_base_src : str = Field(..., description="IML source code of the base component")
|
|
50
|
+
|
|
51
|
+
def short_str(self) -> str:
|
|
52
|
+
return "[bold deep_sky_blue]DomainModelBaseEdit[/bold deep_sky_blue]"
|
|
53
|
+
|
|
54
|
+
def __str__ (self) -> str:
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
# ----------------------------
|
|
58
|
+
# Predicates
|
|
59
|
+
# ----------------------------
|
|
60
|
+
|
|
61
|
+
class PredicateAdd(StateChange):
|
|
62
|
+
""" Add a new predicate """
|
|
63
|
+
pred_type : PredicateType = Field(..., description="Predicate add")
|
|
64
|
+
pred_name : str = Field(..., description="predicate name (no spaces). no signature.")
|
|
65
|
+
pred_src : str = Field(..., description="predicate source code")
|
|
66
|
+
|
|
67
|
+
def short_str(self):
|
|
68
|
+
""" """
|
|
69
|
+
return "[bold deep_blue_sky]PredicateAdd[/bold deep_blue_sky]"
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
class PredicateEdit(StateChange):
|
|
75
|
+
""" Edit an existing predicate """
|
|
76
|
+
pred_name : str = Field(..., description="Predicate name (no spaces)")
|
|
77
|
+
pred_src : str = Field(..., description="Predicate source code. No signature.")
|
|
78
|
+
|
|
79
|
+
def short_str(self):
|
|
80
|
+
return "[bold deep_blue_sky]PredicateEdit[/bold deep_blue_sky]: [bold]name[/bold]={self.pred_name}"
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
return f'PredicateEdit: {self.pred_name}'
|
|
84
|
+
|
|
85
|
+
class PredicateRemove(StateChange):
|
|
86
|
+
""" Remove an existing predicate """
|
|
87
|
+
pred_name : str = Field(..., description="Predicate name to remove")
|
|
88
|
+
|
|
89
|
+
def short_str(self) -> str:
|
|
90
|
+
return f'PredicateRemove: name = {self.pred_name}'
|
|
91
|
+
|
|
92
|
+
def __str__(self) -> str:
|
|
93
|
+
return 'PredicateRemove'
|
|
94
|
+
|
|
95
|
+
# ----------------------------
|
|
96
|
+
# Transitions
|
|
97
|
+
# ----------------------------
|
|
98
|
+
|
|
99
|
+
class TransitionAdd(StateChange):
|
|
100
|
+
""" Add a new transition function """
|
|
101
|
+
trans_name : str = Field(..., description="Transition name")
|
|
102
|
+
trans_src : str = Field(..., description="Transition source code. No signature.")
|
|
103
|
+
|
|
104
|
+
def short_str(self) -> str:
|
|
105
|
+
return ''
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return 'TransitionAdd: '
|
|
109
|
+
|
|
110
|
+
class TransitionEdit(StateChange):
|
|
111
|
+
""" Edit an existing transition function """
|
|
112
|
+
trans_name : str = Field(..., description="Transition name")
|
|
113
|
+
trans_src : str = Field(..., description="Updated transition code. No signature.")
|
|
114
|
+
|
|
115
|
+
def short_str(self):
|
|
116
|
+
return f"[bold deep_blue_sky]TransitionEdit[/bold deep_blue_sky]: [bold]trans_name[/bold]: {self.trans_name}"
|
|
117
|
+
|
|
118
|
+
def __str__(self):
|
|
119
|
+
return "TransitionEdit: "
|
|
120
|
+
|
|
121
|
+
class TransitionRemove(StateChange):
|
|
122
|
+
""" Remove an existing transition function """
|
|
123
|
+
trans_name : str = Field(..., description="Transition name")
|
|
124
|
+
|
|
125
|
+
def short_str(self):
|
|
126
|
+
return f"[bold deep_blue_sky]TransitionRemove[/bold deep_blue_sky]: [bold]trans_name[/bold]: {self.trans_name}"
|
|
127
|
+
|
|
128
|
+
def __str__(self):
|
|
129
|
+
return "TransitionRemove: "
|
|
130
|
+
|
|
131
|
+
# ----------------------------
|
|
132
|
+
# Scenarios
|
|
133
|
+
# ----------------------------
|
|
134
|
+
|
|
135
|
+
class ScenarioAdd(StateChange):
|
|
136
|
+
"""
|
|
137
|
+
Add a new scenario using set-delta semantics.
|
|
138
|
+
For creation, deltas must be add-only (remove must be empty).
|
|
139
|
+
"""
|
|
140
|
+
scenario_name: str
|
|
141
|
+
given: Optional[ScenarioDelta] = None
|
|
142
|
+
when: Optional[ScenarioDelta] = None
|
|
143
|
+
then: Optional[ScenarioDelta] = None
|
|
144
|
+
|
|
145
|
+
@model_validator(mode="after")
|
|
146
|
+
def _add_only(self) -> "ScenarioAdd":
|
|
147
|
+
for sec_name, delta in (("given", self.given), ("when", self.when), ("then", self.then)):
|
|
148
|
+
if delta is not None and not delta.is_add_only():
|
|
149
|
+
raise ValueError(f"ScenarioAdd.{sec_name}: 'remove' is not allowed when creating a scenario")
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
def short_str(self) -> str:
|
|
153
|
+
return f"ScenarioAdd: name={self.scenario_name}"
|
|
154
|
+
|
|
155
|
+
Section = Literal["given", "when", "then"]
|
|
156
|
+
|
|
157
|
+
class ScenarioEdit(StateChange):
|
|
158
|
+
scenario_name: str
|
|
159
|
+
given: ScenarioDelta | None = None
|
|
160
|
+
when: ScenarioDelta | None = None
|
|
161
|
+
then: ScenarioDelta | None = None
|
|
162
|
+
|
|
163
|
+
@model_validator(mode="after")
|
|
164
|
+
def _must_have_some_delta(self) -> "ScenarioEdit":
|
|
165
|
+
deltas = [d for d in (self.given, self.when, self.then) if d is not None]
|
|
166
|
+
|
|
167
|
+
if not deltas:
|
|
168
|
+
raise ValueError("At least one of given/when/then must be provided.")
|
|
169
|
+
|
|
170
|
+
if all(d.is_empty() for d in deltas):
|
|
171
|
+
raise ValueError("ScenarioEdit has no effect: all provided deltas are empty.")
|
|
172
|
+
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
def short_str(self) -> str:
|
|
176
|
+
parts = []
|
|
177
|
+
for sec in ("given", "when", "then"):
|
|
178
|
+
delta = getattr(self, sec)
|
|
179
|
+
if delta:
|
|
180
|
+
if delta.add:
|
|
181
|
+
parts.append(f"{sec} +{delta.add}")
|
|
182
|
+
if delta.remove:
|
|
183
|
+
parts.append(f"{sec} -{delta.remove}")
|
|
184
|
+
return f"ScenarioStepsEdit[{self.scenario_name}]: " + ", ".join(parts)
|
|
185
|
+
|
|
186
|
+
class ScenarioRemove(StateChange):
|
|
187
|
+
""" Remove an existing scenario """
|
|
188
|
+
scenario_name : str = Field(..., description="Name of the scenario to remove")
|
|
189
|
+
|
|
190
|
+
def short_str(self):
|
|
191
|
+
return f"[bold deep_blue_sky]ScenarioRemove[/bold deep_blue_sky]: [bold]Name:[/bold]{self.scenario_name}"
|
|
192
|
+
|
|
193
|
+
# ----------------------------
|
|
194
|
+
# Test traces
|
|
195
|
+
# ----------------------------
|
|
196
|
+
|
|
197
|
+
class RemoveTraceArtifact(StateChange):
|
|
198
|
+
"""Remove an existing trace artifact (TestTrace or LogTrace)."""
|
|
199
|
+
art_id: str = Field(description="Artifact ID")
|
|
200
|
+
|
|
201
|
+
def short_str(self) -> str:
|
|
202
|
+
return (
|
|
203
|
+
f"[bold deep_blue_sky]RemoveTraceArtifact[/bold deep_blue_sky]: "
|
|
204
|
+
f"[bold]ArtID[/bold]: {self.art_id}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
class AddTestTrace(StateChange):
|
|
208
|
+
"""Add a new TestTrace."""
|
|
209
|
+
art_id: str | None = Field(description="Artifact ID")
|
|
210
|
+
|
|
211
|
+
# TraceArtifact core
|
|
212
|
+
given: str = Field(description="Concrete state value (IML expression)")
|
|
213
|
+
when: Optional[str] = Field(default=None, description="Optional concrete action value (IML expression)")
|
|
214
|
+
then: Optional[str] = Field(default=None, description="Optional resulting state value (IML expression)")
|
|
215
|
+
time: str = Field(default="", description="Optional timestamp")
|
|
216
|
+
|
|
217
|
+
# TestTrace fields
|
|
218
|
+
name: str = Field(description="Test case name (not unique)")
|
|
219
|
+
filepath: str = Field(default="", description="Original test filepath")
|
|
220
|
+
language: Optional[str] = Field(default=None, description="Language of the original test source")
|
|
221
|
+
contents: Optional[str] = Field(default=None, description="Original test contents")
|
|
222
|
+
|
|
223
|
+
def short_str(self) -> str:
|
|
224
|
+
return (
|
|
225
|
+
f"[bold deep_blue_sky]AddTestTrace[/bold deep_blue_sky]: "
|
|
226
|
+
f"[bold]ArtID[/bold]: {self.art_id}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
class EditTestTrace(StateChange):
|
|
230
|
+
"""Edit an existing TestTrace (patch semantics: only non-None fields are applied)."""
|
|
231
|
+
art_id: str = Field(description="Artifact ID")
|
|
232
|
+
|
|
233
|
+
# TraceArtifact core (patch)
|
|
234
|
+
given: Optional[str] = Field(default=None, description="Concrete state value (IML expression)")
|
|
235
|
+
when: Optional[str] = Field(default=None, description="Optional concrete action value (IML expression)")
|
|
236
|
+
then: Optional[str] = Field(default=None, description="Optional resulting state value (IML expression)")
|
|
237
|
+
time: Optional[str] = Field(default=None, description="Optional timestamp")
|
|
238
|
+
|
|
239
|
+
# TestTrace fields (patch)
|
|
240
|
+
name: Optional[str] = Field(default=None, description="Test case name (not unique)")
|
|
241
|
+
filepath: Optional[str] = Field(default=None, description="Original test filepath")
|
|
242
|
+
language: Optional[str] = Field(default=None, description="Language of the original test source")
|
|
243
|
+
contents: Optional[str] = Field(default=None, description="Original test contents")
|
|
244
|
+
|
|
245
|
+
@model_validator(mode="after")
|
|
246
|
+
def _must_change_something(self) -> EditTestTrace:
|
|
247
|
+
if (
|
|
248
|
+
self.given is None
|
|
249
|
+
and self.when is None
|
|
250
|
+
and self.then is None
|
|
251
|
+
and self.time is None
|
|
252
|
+
and self.name is None
|
|
253
|
+
and self.filepath is None
|
|
254
|
+
and self.language is None
|
|
255
|
+
and self.contents is None
|
|
256
|
+
):
|
|
257
|
+
raise ValueError(
|
|
258
|
+
"EditTestTrace must provide at least one of: "
|
|
259
|
+
"given, when, then, time, name, filepath, language, or contents"
|
|
260
|
+
)
|
|
261
|
+
return self
|
|
262
|
+
|
|
263
|
+
def short_str(self) -> str:
|
|
264
|
+
return (
|
|
265
|
+
f"[bold deep_blue_sky]EditTestTrace[/bold deep_blue_sky]: "
|
|
266
|
+
f"[bold]ArtID[/bold]: {self.art_id}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# ----------------------------
|
|
270
|
+
# Log traces
|
|
271
|
+
# ----------------------------
|
|
272
|
+
|
|
273
|
+
class AddLogTrace(StateChange):
|
|
274
|
+
"""Add a new LogTrace."""
|
|
275
|
+
art_id: str | None = Field(description="Artifact ID")
|
|
276
|
+
|
|
277
|
+
# TraceArtifact core
|
|
278
|
+
given: str = Field(description="Concrete state value (IML expression)")
|
|
279
|
+
when: Optional[str] = Field(default=None, description="Optional concrete action value (IML expression)")
|
|
280
|
+
then: Optional[str] = Field(default=None, description="Optional resulting state value (IML expression)")
|
|
281
|
+
time: str = Field(default="", description="Optional timestamp")
|
|
282
|
+
|
|
283
|
+
# LogTrace fields
|
|
284
|
+
filename: str = Field(description="Log filename (not unique)")
|
|
285
|
+
contents: str = Field(description="Log entry contents")
|
|
286
|
+
|
|
287
|
+
def short_str(self) -> str:
|
|
288
|
+
return (
|
|
289
|
+
f"[bold deep_blue_sky]AddLogTrace[/bold deep_blue_sky]: "
|
|
290
|
+
f"[bold]ArtID[/bold]: {self.art_id}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
class EditLogTrace(StateChange):
|
|
294
|
+
"""Edit an existing LogTrace (patch semantics: only non-None fields are applied)."""
|
|
295
|
+
art_id: str = Field(description="Artifact ID")
|
|
296
|
+
|
|
297
|
+
# TraceArtifact core (patch)
|
|
298
|
+
given: Optional[str] = Field(default=None, description="Concrete state value (IML expression)")
|
|
299
|
+
when: Optional[str] = Field(default=None, description="Optional concrete action value (IML expression)")
|
|
300
|
+
then: Optional[str] = Field(default=None, description="Optional resulting state value (IML expression)")
|
|
301
|
+
time: Optional[str] = Field(default=None, description="Optional timestamp")
|
|
302
|
+
|
|
303
|
+
# LogTrace fields (patch)
|
|
304
|
+
filename: Optional[str] = Field(default=None, description="Log filename (not unique)")
|
|
305
|
+
contents: Optional[str] = Field(default=None, description="Log entry contents")
|
|
306
|
+
|
|
307
|
+
@model_validator(mode="after")
|
|
308
|
+
def _must_change_something(self) -> EditLogTrace:
|
|
309
|
+
if (
|
|
310
|
+
self.given is None
|
|
311
|
+
and self.when is None
|
|
312
|
+
and self.then is None
|
|
313
|
+
and self.time is None
|
|
314
|
+
and self.filename is None
|
|
315
|
+
and self.contents is None
|
|
316
|
+
):
|
|
317
|
+
raise ValueError(
|
|
318
|
+
"EditLogTrace must provide at least one of: "
|
|
319
|
+
"given, when, then, time, filename, or contents"
|
|
320
|
+
)
|
|
321
|
+
return self
|
|
322
|
+
|
|
323
|
+
def short_str(self) -> str:
|
|
324
|
+
return (
|
|
325
|
+
f"[bold deep_blue_sky]EditLogTrace[/bold deep_blue_sky]: "
|
|
326
|
+
f"[bold]ArtID[/bold]: {self.art_id}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# ----------------------------
|
|
330
|
+
# RemoveDataArtifact
|
|
331
|
+
# ----------------------------
|
|
332
|
+
|
|
333
|
+
class RemoveDataArtifact(StateChange):
|
|
334
|
+
""" Remove an existing data artifact """
|
|
335
|
+
art_id: str = Field(description="Artifact ID")
|
|
336
|
+
def short_str(self) -> str:
|
|
337
|
+
return "[bold deep_blue_sky]RemoveDataArtifact[/bold deep_blue_sky]"
|
|
338
|
+
|
|
339
|
+
# ----------------------------
|
|
340
|
+
# Documentation references
|
|
341
|
+
# ----------------------------
|
|
342
|
+
|
|
343
|
+
class AddDocRef(StateChange):
|
|
344
|
+
"""Add a new documentation reference artifact."""
|
|
345
|
+
art_id: str | None = Field(description="Artifact ID")
|
|
346
|
+
text: str = Field(description="Documentation text")
|
|
347
|
+
meta: str | None = Field(default=None, description="Optional metadata")
|
|
348
|
+
|
|
349
|
+
def short_str(self) -> str:
|
|
350
|
+
return f"[bold deep_blue_sky]AddDocRef[/bold deep_blue_sky]: [bold]ArtID[/bold]: {self.art_id}"
|
|
351
|
+
|
|
352
|
+
class EditDocRef(StateChange):
|
|
353
|
+
"""Edit an existing DocRef."""
|
|
354
|
+
art_id: str = Field(description="Artifact ID")
|
|
355
|
+
text: str | None = Field(default=None, description="New documentation text")
|
|
356
|
+
meta: str | None = Field(default=None, description="New metadata")
|
|
357
|
+
|
|
358
|
+
@model_validator(mode="after")
|
|
359
|
+
def _must_change_something(self) -> "EditDocRef":
|
|
360
|
+
if self.text is None and self.meta is None:
|
|
361
|
+
raise ValueError("EditDocRef must provide at least one of: text or meta")
|
|
362
|
+
return self
|
|
363
|
+
|
|
364
|
+
def short_str(self) -> str:
|
|
365
|
+
return f"[bold deep_blue_sky]EditDocRef[/bold deep_blue_sky]: [bold]ArtID[/bold]: {self.art_id}"
|
|
366
|
+
|
|
367
|
+
# ----------------------------
|
|
368
|
+
# Source code references
|
|
369
|
+
# ----------------------------
|
|
370
|
+
|
|
371
|
+
class AddSrcCodeRef(StateChange):
|
|
372
|
+
"""Add a new source code reference artifact."""
|
|
373
|
+
art_id: str | None = Field(description="Artifact ID")
|
|
374
|
+
src_code: str = Field(description="Source code")
|
|
375
|
+
language: str | None = Field(default=None, description="Programming language")
|
|
376
|
+
file_path: str | None = Field(default=None, description="Optional file path")
|
|
377
|
+
iml_code: str | None = Field(default=None, description="Optional IML formalization")
|
|
378
|
+
meta: str | None = Field(default=None, description="Optional metadata")
|
|
379
|
+
|
|
380
|
+
def short_str(self) -> str:
|
|
381
|
+
return f"[bold deep_blue_sky]AddSrcCodeRef[/bold deep_blue_sky]: [bold]ArtID[/bold]: {self.art_id}"
|
|
382
|
+
|
|
383
|
+
class EditSrcCodeRef(StateChange):
|
|
384
|
+
"""Edit an existing SrcCodeRef."""
|
|
385
|
+
art_id: str = Field(description="Artifact ID")
|
|
386
|
+
src_code: str | None = Field(default=None, description="Updated source code")
|
|
387
|
+
language: str | None = Field(default=None, description="Updated language")
|
|
388
|
+
file_path: str | None = Field(default=None, description="Updated file path")
|
|
389
|
+
iml_code: str | None = Field(default=None, description="Updated IML formalization")
|
|
390
|
+
meta: str | None = Field(default=None, description="Updated metadata")
|
|
391
|
+
|
|
392
|
+
@model_validator(mode="after")
|
|
393
|
+
def _must_change_something(self) -> EditSrcCodeRef:
|
|
394
|
+
if (
|
|
395
|
+
self.src_code is None
|
|
396
|
+
and self.language is None
|
|
397
|
+
and self.file_path is None
|
|
398
|
+
and self.iml_code is None
|
|
399
|
+
and self.meta is None
|
|
400
|
+
):
|
|
401
|
+
raise ValueError(
|
|
402
|
+
"EditSrcCodeRef must provide at least one of: "
|
|
403
|
+
"src_code, language, file_path, iml_code, or meta"
|
|
404
|
+
)
|
|
405
|
+
return self
|
|
406
|
+
|
|
407
|
+
def short_str(self) -> str:
|
|
408
|
+
return f"[bold deep_blue_sky]EditSrcCodeRef[/bold deep_blue_sky]: [bold]ArtID[/bold]: {self.art_id}"
|
|
409
|
+
|
|
410
|
+
# ----------------------------
|
|
411
|
+
# Links in the ArtifactMap
|
|
412
|
+
# ----------------------------
|
|
413
|
+
|
|
414
|
+
class LinkArtifactsComponents(StateChange):
|
|
415
|
+
""" Add a link between a non-executable arifact (e.g. source code or documentation) and a model component """
|
|
416
|
+
art_id : str = Field(description="Artifact ID")
|
|
417
|
+
comp_name : str = Field(description="Component name")
|
|
418
|
+
|
|
419
|
+
def short_str(self) -> str:
|
|
420
|
+
return "[bold deep_blue_sky]LinkArtifactComponents[/bold deep_blue_sky]"
|
|
421
|
+
|
|
422
|
+
class RemoveArtComponentLink(StateChange):
|
|
423
|
+
""" Remove a link between a non-executable arifact (e.g. source code or documentation) and a model component """
|
|
424
|
+
art_id : str = Field(description="Artifact ID")
|
|
425
|
+
comp_name : str = Field(description="Component name")
|
|
426
|
+
|
|
427
|
+
def short_str(self) -> str:
|
|
428
|
+
return "[bold deep_blue_sky]RemoveArtComponentLink[/bold deep_blue_sky]"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/state/change_result.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Literal, TypedDict
|
|
10
|
+
|
|
11
|
+
from ..state.change import StateChange
|
|
12
|
+
from ..state.inst import StateInstance
|
|
13
|
+
from ..state.diff import StateDiff
|
|
14
|
+
from typing import Union
|
|
15
|
+
|
|
16
|
+
class ProcessChangeError(TypedDict):
|
|
17
|
+
ok: Literal[False]
|
|
18
|
+
stage: Literal["apply_change", "analyze_change", "calc_diff", "refresh_stats"]
|
|
19
|
+
error: Exception
|
|
20
|
+
change: StateChange
|
|
21
|
+
old_state: StateInstance
|
|
22
|
+
|
|
23
|
+
class ProcessChangeSuccess(TypedDict):
|
|
24
|
+
ok: Literal[True]
|
|
25
|
+
stage: Literal["ok"]
|
|
26
|
+
change: StateChange
|
|
27
|
+
old_state: StateInstance
|
|
28
|
+
new_state: StateInstance
|
|
29
|
+
state_diff: StateDiff | None # this is because it could potentially be the first state instance
|
|
30
|
+
new_num_instances: int
|
|
31
|
+
|
|
32
|
+
ProcessChangeResult = Union[ProcessChangeError, ProcessChangeSuccess]
|