statable 3.1.0__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.
Files changed (117) hide show
  1. codegen/__init__.py +38 -0
  2. codegen/c_code_generator.py +1752 -0
  3. codegen/code_merger.py +371 -0
  4. codegen/code_templates.py +509 -0
  5. codegen/config.py +167 -0
  6. codegen/enum_generator.py +377 -0
  7. codegen/event_queue_generator.py +303 -0
  8. codegen/interrupt_generator.py +345 -0
  9. codegen/naming_convention.py +171 -0
  10. codegen/osal_generator.py +282 -0
  11. codegen/role_function_generator.py +1244 -0
  12. codegen/sample_data.py +269 -0
  13. codegen/state_actions_generator.py +395 -0
  14. codegen/struct_generator.py +721 -0
  15. codegen/timer_generator.py +299 -0
  16. codegen/transition_generator.py +907 -0
  17. codegen/type_mapper.py +69 -0
  18. codegen/validate/__init__.py +38 -0
  19. codegen/validate/change_actions.py +90 -0
  20. codegen/validate/change_applier.py +416 -0
  21. codegen/validate/clipboard_manager.py +47 -0
  22. codegen/validate/data/__init__.py +30 -0
  23. codegen/validate/data/action_definitions.py +200 -0
  24. codegen/validate/data/keywords.py +46 -0
  25. codegen/validate/data/prompt_templates.py +176 -0
  26. codegen/validate/data/validation_rules.py +205 -0
  27. codegen/validate/items/__init__.py +30 -0
  28. codegen/validate/items/base_validator.py +25 -0
  29. codegen/validate/items/cell_validator.py +275 -0
  30. codegen/validate/items/custom_type_validator.py +75 -0
  31. codegen/validate/items/event_validator.py +78 -0
  32. codegen/validate/items/flag_validator.py +74 -0
  33. codegen/validate/items/interrupt_validator.py +75 -0
  34. codegen/validate/items/queue_validator.py +73 -0
  35. codegen/validate/items/role_function_validator.py +175 -0
  36. codegen/validate/items/state_validator.py +88 -0
  37. codegen/validate/items/timer_validator.py +120 -0
  38. codegen/validate/items/transition_validator.py +113 -0
  39. codegen/validate/items/variable_validator.py +132 -0
  40. codegen/validate/logger.py +52 -0
  41. codegen/validate/models.py +177 -0
  42. codegen/validate/prompt_generator.py +724 -0
  43. codegen/validate/response_parser.py +154 -0
  44. codegen/validate/response_validator.py +275 -0
  45. codegen/validate/validation_dialog.py +322 -0
  46. codegen/validate/validator.py +94 -0
  47. codegen/variable_generator.py +419 -0
  48. statable/__init__.py +71 -0
  49. statable/__main__.py +49 -0
  50. statable/cli.py +251 -0
  51. statable/global_defs.py +256 -0
  52. statable/mermaid_gen.py +144 -0
  53. statable/model.py +426 -0
  54. statable/parser.py +64 -0
  55. statable/sample_data.py +435 -0
  56. statable/shared/__init__.py +24 -0
  57. statable/shared/condition_library.py +63 -0
  58. statable/shared/literal_library.py +66 -0
  59. statable/shared/role_function_library.py +150 -0
  60. statable/state_machine.py +121 -0
  61. statable/xml_io.py +1164 -0
  62. statable-3.1.0.dist-info/METADATA +151 -0
  63. statable-3.1.0.dist-info/RECORD +117 -0
  64. statable-3.1.0.dist-info/WHEEL +5 -0
  65. statable-3.1.0.dist-info/entry_points.txt +2 -0
  66. statable-3.1.0.dist-info/top_level.txt +3 -0
  67. statable_gui/__init__.py +23 -0
  68. statable_gui/action_edit_dialog.py +214 -0
  69. statable_gui/code_generation_dialog.py +486 -0
  70. statable_gui/code_generation_settings_dialog.py +522 -0
  71. statable_gui/common_widgets.py +537 -0
  72. statable_gui/condition_builder_dialog.py +500 -0
  73. statable_gui/condition_edit_dialog.py +123 -0
  74. statable_gui/condition_template_dialog.py +58 -0
  75. statable_gui/config.py +21 -0
  76. statable_gui/dialogs.py +439 -0
  77. statable_gui/event_definition_dialog.py +605 -0
  78. statable_gui/event_delivery_settings_dialog.py +191 -0
  79. statable_gui/event_queue_dialog.py +245 -0
  80. statable_gui/global_defs.py +167 -0
  81. statable_gui/global_defs_dialog.py +796 -0
  82. statable_gui/i18n/__init__.py +118 -0
  83. statable_gui/interrupt_handler_edit_dialog.py +810 -0
  84. statable_gui/layer_settings_dialog.py +180 -0
  85. statable_gui/libcntrl/__init__.py +23 -0
  86. statable_gui/libcntrl/condition_library.py +13 -0
  87. statable_gui/libcntrl/literal_library.py +13 -0
  88. statable_gui/libcntrl/literal_management_dialog.py +185 -0
  89. statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
  90. statable_gui/libcntrl/role_function_library.py +13 -0
  91. statable_gui/literal_definition_dialog.py +62 -0
  92. statable_gui/logger.py +83 -0
  93. statable_gui/main_window.py +1380 -0
  94. statable_gui/matrix_table.py +413 -0
  95. statable_gui/preference_keys.py +18 -0
  96. statable_gui/preferences.py +74 -0
  97. statable_gui/role_function_dialog.py +244 -0
  98. statable_gui/sample_data.py +11 -0
  99. statable_gui/state_actions_dialog.py +398 -0
  100. statable_gui/symbol_picker.py +170 -0
  101. statable_gui/traceball.py +37 -0
  102. statable_gui/transition_editor_direct/__init__.py +15 -0
  103. statable_gui/transition_editor_direct/actions_tab.py +856 -0
  104. statable_gui/transition_editor_direct/code_widget.py +344 -0
  105. statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
  106. statable_gui/transition_editor_direct/dialog.py +335 -0
  107. statable_gui/transition_editor_direct/draft.py +270 -0
  108. statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
  109. statable_gui/transition_editor_direct/overview_tab.py +173 -0
  110. statable_gui/transition_editor_direct/palette_widget.py +233 -0
  111. statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
  112. statable_gui/transition_editor_direct/relations_tab.py +230 -0
  113. statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
  114. statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
  115. statable_gui/transition_editor_direct/transitions_tab.py +620 -0
  116. statable_gui/validation_dialog.py +285 -0
  117. statable_gui/widgets.py +1315 -0
statable/model.py ADDED
@@ -0,0 +1,426 @@
1
+ """
2
+ statable/model.py
3
+ Data model for StaTable.
4
+
5
+ Version History
6
+ ---------------
7
+ v1.0 - Initial data model.
8
+ v1.6 - Transition: kw_only=True. Prevents positional-argument
9
+ field-order mistakes (see v1.4 §9.6 #67).
10
+ v2.2 - State.entry / State.exit: str -> List[str].
11
+ ActionStep and TransitionRelation added.
12
+ v2.2.4 - ActionStep / TransitionRelation: kw_only=True.
13
+ v3.7 - Reserved-field annotations added:
14
+ * State.do
15
+ * RoleFunction.return_type / arg1_type / arg1_name
16
+ / arg2_type / arg2_name
17
+ These fields are not exposed in the GUI and not used by
18
+ the code generator, but are retained in the data model
19
+ and XML I/O for backward compatibility.
20
+ v3.8 - RoleFunction: added used_global_vars / used_events /
21
+ used_literals. These mirror libcntrl.RoleFunction and
22
+ enable the GUI dialog to track symbol references (see
23
+ statable_gui/role_function_dialog.py v3.9). Persisted in
24
+ XML (xml_io.py v3.8); not consumed by the code generator.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass, field
30
+ from enum import Enum
31
+ from typing import Optional, List
32
+
33
+
34
+ class StateType(Enum):
35
+ NORMAL = "normal"
36
+ CONCURRENT = "concurrent"
37
+ REGION = "region"
38
+ INITIAL = "initial"
39
+ FINAL = "final"
40
+ CHOICE = "choice"
41
+ JUNCTION = "junction"
42
+
43
+
44
+ class EventKind(Enum):
45
+ SIGNAL = "signal"
46
+ CALL = "call"
47
+ TIME = "time"
48
+ CHANGE = "change"
49
+
50
+
51
+ class EventDeliveryType(Enum):
52
+ """Event delivery method"""
53
+ DIRECT = "direct"
54
+ QUEUE = "queue"
55
+ DOUBLE = "double"
56
+
57
+
58
+ class EventSourceLayer(Enum):
59
+ """Event source layer"""
60
+ DRIVER = "driver"
61
+ MIDDLEWARE = "middleware"
62
+
63
+
64
+ def _normalize_action_list(value) -> list:
65
+ """[v2.7.0] Normalize a legacy action list to List[ActionStep].
66
+
67
+ Accepts:
68
+ - None -> []
69
+ - str -> [ActionStep(role_function=str)]
70
+ - list of str -> [ActionStep(role_function=s) ...]
71
+ - list of ActionStep -> as-is
72
+ - list of dict -> [ActionStep.from_dict(d) ...]
73
+ """
74
+ if value is None:
75
+ return []
76
+ if isinstance(value, str):
77
+ return [ActionStep(role_function=value)] if value.strip() else []
78
+ if isinstance(value, list):
79
+ result = []
80
+ for item in value:
81
+ if isinstance(item, str):
82
+ if item.strip():
83
+ result.append(ActionStep(role_function=item))
84
+ elif isinstance(item, ActionStep):
85
+ result.append(item)
86
+ elif isinstance(item, dict):
87
+ result.append(ActionStep.from_dict(item))
88
+ return result
89
+ return []
90
+
91
+
92
+ @dataclass
93
+ class State:
94
+ """State definition.
95
+
96
+ [v2.2 change]
97
+ entry / exit: str -> List[str]
98
+ Multiple entry / exit actions are supported.
99
+ For defensive compatibility, str / None are auto-normalized
100
+ in __post_init__.
101
+
102
+ [v3.7 note]
103
+ `do` is a **reserved field**. The code generator does not
104
+ reference it, and it is not exposed in the GUI SettingsPanel
105
+ (the "do function" column was removed in v3.7). It remains
106
+ in the data model and in XML I/O for backward compatibility
107
+ with projects created before v3.7.
108
+ """
109
+ name: str
110
+ type: StateType = StateType.NORMAL
111
+ parent: Optional[str] = None
112
+ # [v2.7.0] List[str] -> List[ActionStep]. Legacy str / list-of-str
113
+ # values are auto-normalized in __post_init__.
114
+ entry: List[ActionStep] = field(default_factory=list)
115
+ exit: List[ActionStep] = field(default_factory=list)
116
+ # [v2.7.0] do activity: executed every loop while the state is active.
117
+ do_actions: List[ActionStep] = field(default_factory=list)
118
+ # [Reserved] Not exposed in UI / not used by codegen.
119
+ # Persisted in XML for backward compatibility only.
120
+ do: str = ""
121
+ description: str = ""
122
+
123
+ def __post_init__(self):
124
+ # v2.2 + v2.7.0: defensive normalization to List[ActionStep]
125
+ self.entry = _normalize_action_list(self.entry)
126
+ self.exit = _normalize_action_list(self.exit)
127
+ self.do_actions = _normalize_action_list(self.do_actions)
128
+
129
+
130
+ @dataclass
131
+ class EventTrigger:
132
+ """[C-51 Step 3] Structured trigger detail for an Event.
133
+
134
+ Only `type` is required; other fields are type-specific.
135
+
136
+ type:
137
+ "manual" - fires by explicit user code (default)
138
+ "edge" - GPIO edge detection
139
+ "polling" - periodic polling
140
+ "timer" - timer expiry
141
+ "call" - function invocation
142
+ "comparison" - condition polling
143
+ """
144
+ type: str = "manual"
145
+ source: str = ""
146
+ description: str = ""
147
+ edge: str = "" # edge: rising / falling / both
148
+ debounce_ms: int = 0 # edge
149
+ period_ms: int = 0 # polling / timer
150
+ auto_reload: bool = True # timer
151
+ caller: str = "" # call
152
+ condition: str = "" # comparison
153
+ poll_period_ms: int = 0 # comparison
154
+
155
+ def to_dict(self) -> dict:
156
+ """Serialize non-empty fields only (order stable)."""
157
+ d = {"type": self.type}
158
+ if self.source: d["source"] = self.source
159
+ if self.description: d["description"] = self.description
160
+ if self.edge: d["edge"] = self.edge
161
+ if self.debounce_ms: d["debounce_ms"] = self.debounce_ms
162
+ if self.period_ms: d["period_ms"] = self.period_ms
163
+ if self.type == "timer" and not self.auto_reload:
164
+ d["auto_reload"] = False
165
+ if self.caller: d["caller"] = self.caller
166
+ if self.condition: d["condition"] = self.condition
167
+ if self.poll_period_ms: d["poll_period_ms"] = self.poll_period_ms
168
+ return d
169
+
170
+ @classmethod
171
+ def from_dict(cls, d: dict) -> "EventTrigger":
172
+ def _int(v, default=0):
173
+ try:
174
+ return int(v)
175
+ except (TypeError, ValueError):
176
+ return default
177
+ def _bool(v, default=True):
178
+ if isinstance(v, bool):
179
+ return v
180
+ if isinstance(v, str):
181
+ return v.lower() not in ("false", "0", "")
182
+ return bool(v) if v is not None else default
183
+ return cls(
184
+ type=str(d.get("type", "manual") or "manual"),
185
+ source=str(d.get("source", "") or ""),
186
+ description=str(d.get("description", "") or ""),
187
+ edge=str(d.get("edge", "") or ""),
188
+ debounce_ms=_int(d.get("debounce_ms", 0)),
189
+ period_ms=_int(d.get("period_ms", 0)),
190
+ auto_reload=_bool(d.get("auto_reload", True)),
191
+ caller=str(d.get("caller", "") or ""),
192
+ condition=str(d.get("condition", "") or ""),
193
+ poll_period_ms=_int(d.get("poll_period_ms", 0)),
194
+ )
195
+
196
+
197
+ @dataclass
198
+ class Event:
199
+ """State transition event"""
200
+ name: str
201
+ id: Optional[int] = None
202
+ kind: EventKind = EventKind.SIGNAL
203
+ params: List[str] = field(default_factory=list)
204
+ priority: int = 0
205
+ description: str = ""
206
+ delivery_type: EventDeliveryType = EventDeliveryType.DIRECT
207
+ source_layer: EventSourceLayer = EventSourceLayer.DRIVER
208
+ data_type: str = ""
209
+ data_name: str = ""
210
+ title: str = ""
211
+ # [C-51 Step 2] Free-text description of when / from where
212
+ # this event fires. Only meaningful for kind in
213
+ # {signal, call, time}; kind=change is handled inside
214
+ # transition conditions. Metadata only; not consumed
215
+ # by codegen. Backward compatible (missing XML attribute
216
+ # loads as "").
217
+ trigger: str = ""
218
+ # [C-51 Step 3] Structured trigger detail. None = not specified
219
+ # (backward-compatible with Step 2, where only `trigger` existed).
220
+ trigger_detail: Optional["EventTrigger"] = None
221
+
222
+ def __post_init__(self):
223
+ if not self.title:
224
+ self.title = f"Event: {self.name}" if self.name else "Event: (completion)"
225
+
226
+
227
+ @dataclass(kw_only=True)
228
+ class Transition:
229
+ """State transition definition.
230
+
231
+ [v1.6 change] kw_only=True
232
+ To structurally prevent positional-argument field-order accidents
233
+ (v1.4 section 9.6 #67), only kw_only arguments are accepted.
234
+
235
+ [v2.2 additions]
236
+ early_return: bool = False
237
+ - True : "Commit" - stop evaluating later transitions in the same cell
238
+ - False : "Tentative" - later transitions may overwrite the target
239
+
240
+ label: str = ""
241
+ Stable identifier within a cell (e.g. "T1", "T2").
242
+ Referenced by TransitionRelation.members.
243
+ Persisted in XML.
244
+ """
245
+ source: str
246
+ event: str
247
+ condition: str = ""
248
+ pre_actions: List[str] = field(default_factory=list)
249
+ target: str = ""
250
+ has_else: bool = True
251
+ else_target: str = ""
252
+ else_actions: List[str] = field(default_factory=list)
253
+ early_return: bool = False
254
+ label: str = ""
255
+ action: str = ""
256
+ transition_type: str = "external"
257
+ title: str = ""
258
+
259
+ def __post_init__(self):
260
+ if not self.title:
261
+ self.title = "(untitled transition)"
262
+
263
+
264
+ @dataclass(kw_only=True)
265
+ class ActionStep:
266
+ """Transition-independent action step (v2.2).
267
+
268
+ [v2.2.4 change] kw_only=True
269
+ To structurally prevent positional-argument field-order accidents
270
+ (consistent with v1.5 RoleFunction / v1.6 Transition).
271
+
272
+ trigger:
273
+ "before_transitions" - runs just before evaluating transitions
274
+ "after_transitions" - runs at the end of the cell body
275
+
276
+ Note: legacy "always" is silently mapped to "before_transitions"
277
+ when loading old XML.
278
+ """
279
+ role_function: str = ""
280
+ trigger: str = "before_transitions"
281
+ title: str = ""
282
+ # [v2.7.0] State action extensions
283
+ condition: str = "" # execution condition (C expression)
284
+ action_type: str = "role" # "role" | "fire_event" | "custom"
285
+ event_name: str = "" # action_type="fire_event"
286
+
287
+ def __post_init__(self):
288
+ if not self.title:
289
+ self.title = self.role_function or "(untitled action)"
290
+
291
+ def to_dict(self) -> dict:
292
+ """[v2.7.0] Serialize non-empty fields only (order stable)."""
293
+ d = {}
294
+ if self.role_function: d["role_function"] = self.role_function
295
+ if self.trigger and self.trigger != "before_transitions":
296
+ d["trigger"] = self.trigger
297
+ if self.title: d["title"] = self.title
298
+ if self.condition: d["condition"] = self.condition
299
+ if self.action_type and self.action_type != "role":
300
+ d["action_type"] = self.action_type
301
+ if self.event_name: d["event_name"] = self.event_name
302
+ return d
303
+
304
+ @classmethod
305
+ def from_dict(cls, d: dict) -> "ActionStep":
306
+ """[v2.7.0] Deserialize; tolerant of legacy `name` attribute."""
307
+ role = str(d.get("role_function", "") or d.get("name", "") or "")
308
+ return cls(
309
+ role_function=role,
310
+ trigger=str(d.get("trigger", "before_transitions") or "before_transitions"),
311
+ title=str(d.get("title", "") or ""),
312
+ condition=str(d.get("condition", "") or ""),
313
+ action_type=str(d.get("action_type", "role") or "role"),
314
+ event_name=str(d.get("event_name", "") or ""),
315
+ )
316
+
317
+
318
+ @dataclass(kw_only=True)
319
+ class TransitionRelation:
320
+ """Relation between transitions in one cell (v2.2).
321
+
322
+ [v2.2.4 change] kw_only=True
323
+ To structurally prevent positional-argument field-order accidents
324
+ (consistent with v1.5 RoleFunction / v1.6 Transition).
325
+
326
+ kind:
327
+ "sequential" - evaluate members in order (default)
328
+ "exclusive" - at most one member fires; codegen enforces early return
329
+ "group" - logical grouping; shared_condition is hoisted
330
+ as an outer if (evaluated once)
331
+
332
+ members: Transition.label values (e.g. ["T1", "T2"]).
333
+ shared_condition: only used when kind == "group".
334
+
335
+ [v2.2 §12-5] children: nested sub-relations (recursive).
336
+ """
337
+ kind: str = "sequential"
338
+ members: List[str] = field(default_factory=list)
339
+ shared_condition: str = ""
340
+ note: str = ""
341
+ children: List["TransitionRelation"] = field(default_factory=list)
342
+
343
+
344
+ @dataclass(kw_only=True)
345
+ class RoleFunction:
346
+ """Role function (shared library / state machine local).
347
+
348
+ [v3.7 note]
349
+ The C signature generated for a role function is **fixed** to
350
+
351
+ int RoleFunc_<NS>_<Name>(const Transition_t* transition,
352
+ Context_t* ctx);
353
+
354
+ by `codegen/role_function_generator.py`, which does not
355
+ reference `return_type` / `arg1_*` / `arg2_*` at all.
356
+ Those fields are therefore **reserved**:
357
+
358
+ - They are not exposed in the GUI SettingsPanel or in
359
+ RoleFunctionDialog (v3.7 / v3.8).
360
+ - They are still serialized / deserialized by xml_io.py
361
+ for backward compatibility with projects created
362
+ before v3.7.
363
+ - `SettingsPanel.apply_changes()` and `RoleFunctionDialog.
364
+ get_role_function()` carry existing values forward so
365
+ that an XML round-trip remains lossless.
366
+
367
+ Do not remove these fields without a migration plan; old
368
+ project files may rely on their presence.
369
+
370
+ [v3.8 addition]
371
+ used_global_vars / used_events / used_literals mirror the
372
+ fields of libcntrl.RoleFunction. They let the RoleFunctionDialog
373
+ track which symbols a role function references, without
374
+ changing the generated C signature (which remains fixed as
375
+ above). Persisted in XML; not consumed by the code generator.
376
+ """
377
+ name: str
378
+ namespace: str = ""
379
+ description: str = ""
380
+ # [Reserved] Not exposed in UI / not used by codegen.
381
+ # Persisted in XML for backward compatibility only.
382
+ return_type: str = "void"
383
+ # [Reserved] Not exposed in UI / not used by codegen.
384
+ arg1_type: str = ""
385
+ # [Reserved] Not exposed in UI / not used by codegen.
386
+ arg1_name: str = ""
387
+ # [Reserved] Not exposed in UI / not used by codegen.
388
+ arg2_type: str = ""
389
+ # [Reserved] Not exposed in UI / not used by codegen.
390
+ arg2_name: str = ""
391
+ title: str = ""
392
+ # [v3.8] GUI symbol tracking (mirrors libcntrl.RoleFunction).
393
+ # Persisted in XML; not consumed by codegen.
394
+ used_global_vars: List[str] = field(default_factory=list)
395
+ used_events: List[str] = field(default_factory=list)
396
+ used_literals: List[str] = field(default_factory=list)
397
+
398
+ def __post_init__(self):
399
+ if not self.title:
400
+ self.title = f"Role function: {self.qualified_name}"
401
+ # v3.8: defensive normalization
402
+ if self.used_global_vars is None:
403
+ self.used_global_vars = []
404
+ if self.used_events is None:
405
+ self.used_events = []
406
+ if self.used_literals is None:
407
+ self.used_literals = []
408
+
409
+ @property
410
+ def qualified_name(self) -> str:
411
+ if self.namespace:
412
+ return f"{self.namespace}.{self.name}"
413
+ return self.name
414
+
415
+ @classmethod
416
+ def from_legacy_name(cls, legacy_name: str,
417
+ layer_names: Optional[List[str]] = None) -> 'RoleFunction':
418
+ if not legacy_name:
419
+ return cls(name="")
420
+ if layer_names:
421
+ for layer in layer_names:
422
+ prefix = f"{layer}_"
423
+ if legacy_name.startswith(prefix):
424
+ return cls(name=legacy_name[len(prefix):],
425
+ namespace=layer)
426
+ return cls(name=legacy_name, namespace="")
statable/parser.py ADDED
@@ -0,0 +1,64 @@
1
+ # statable/parser.py
2
+ """
3
+ StaTable parser (unimplemented stub)
4
+
5
+ [v1.8 section 11.2 #9]
6
+ Currently an unimplemented stub.
7
+ XML I/O is handled by `statable/xml_io.py`.
8
+
9
+ [Planned for future implementation]
10
+ - Excel (.xlsx) loading
11
+ Import state transition tables using openpyxl etc.
12
+ - CSV (.csv) loading
13
+ Assume state x event matrix format
14
+ - JSON (.json) loading
15
+ General-purpose format for external tool integration
16
+
17
+ [Notes]
18
+ - This module currently has no callers
19
+ - When implementing, convert to `statable/model.py`'s
20
+ StateMachine / State / Event / Transition
21
+ - It is desirable to target the same interface as the existing `xml_io.py`
22
+ (equivalent to project_to_xml / project_from_xml)
23
+
24
+ [If not implementing]
25
+ This file itself can be deleted without issue.
26
+ Since there are no callers, deletion has no impact.
27
+ """
28
+
29
+ # ======================================================================
30
+ # Unimplemented marker
31
+ # ======================================================================
32
+ # The following are placeholders.
33
+ # Do not call until implemented.
34
+
35
+ __all__ = [] # No public API (not implemented)
36
+
37
+
38
+ class ParserNotImplementedError(NotImplementedError):
39
+ """Exception indicating parser.py features not implemented"""
40
+ pass
41
+
42
+
43
+ def parse_excel(filepath: str):
44
+ """\n Load state transition data from an Excel file (not implemented)\n"""
45
+ raise ParserNotImplementedError(
46
+ "parse_excel is not implemented."
47
+ "Currently, please use statable/xml_io.py."
48
+ )
49
+
50
+
51
+ def parse_csv(filepath: str):
52
+ """\n Load state transition data from a CSV file (not implemented)\n"""
53
+ raise ParserNotImplementedError(
54
+ "parse_csv is not implemented."
55
+ "Currently, please use statable/xml_io.py."
56
+ )
57
+
58
+
59
+ def parse_json(filepath: str):
60
+ """\n Load state transition data from a JSON file (not implemented)\n"""
61
+ raise ParserNotImplementedError(
62
+ "parse_json is not implemented."
63
+ "Currently, please use statable/xml_io.py."
64
+ )