diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6f9f6eb..e70b8469 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,11 +37,10 @@ repos: uv run python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine docs/images/readme_trafficlightmachine.png + --events cycle cycle cycle language: system pass_filenames: false - files: >- - (statemachine/contrib/diagram/ - |tests/examples/traffic_light_machine\.py) + files: (statemachine/contrib/diagram/|tests/examples/traffic_light_machine\.py) - id: pytest name: Pytest entry: uv run pytest -n auto --cov-fail-under=100 diff --git a/docs/diagram.md b/docs/diagram.md index d607e339..6b09b1ba 100644 --- a/docs/diagram.md +++ b/docs/diagram.md @@ -103,6 +103,13 @@ The output format is inferred from the file extension: python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png ``` +To highlight the current state, use `--events` to instantiate the machine and +send events before rendering: + +```bash +python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle +``` + ## Sphinx directive diff --git a/docs/images/readme_trafficlightmachine.png b/docs/images/readme_trafficlightmachine.png index 6519080d..5685ec10 100644 Binary files a/docs/images/readme_trafficlightmachine.png and b/docs/images/readme_trafficlightmachine.png differ diff --git a/docs/releases/3.1.0.md b/docs/releases/3.1.0.md index 77c702b2..34c6a3f5 100644 --- a/docs/releases/3.1.0.md +++ b/docs/releases/3.1.0.md @@ -49,6 +49,13 @@ machine instance concurrently. This is now documented in the [#592](https://github.com/fgmacedo/python-statemachine/pull/592). +### Diagram CLI `--events` option + +The `python -m statemachine.contrib.diagram` command now accepts `--events` to +instantiate the machine and send events before rendering, highlighting the +current active state — matching the Sphinx directive's `:events:` option. +See {ref}`diagram:Command line` for details. + ### Bugfixes in 3.1.0 - Fixes silent misuse of `Event()` with multiple positional arguments. Passing more than one diff --git a/statemachine/contrib/diagram/__init__.py b/statemachine/contrib/diagram/__init__.py index 51e7bc78..a2e31a71 100644 --- a/statemachine/contrib/diagram/__init__.py +++ b/statemachine/contrib/diagram/__init__.py @@ -135,7 +135,7 @@ def import_sm(qualname): return smclass -def write_image(qualname, out): +def write_image(qualname, out, events=None): """ Given a `qualname`, that is the fully qualified dotted path to a StateMachine classes, imports the class and generates a dot graph using the `pydot` lib. @@ -143,10 +143,20 @@ def write_image(qualname, out): open/create and truncate such file and write on it a representation of the graph defined by the statemachine, in the format specified by the extension contained in the out path (out.ext). + + If `events` is provided, the machine is instantiated and each event is sent + before rendering, so the diagram highlights the current active state. """ smclass = import_sm(qualname) - graph = DotGraphMachine(smclass).get_graph() + if events: + machine = smclass() + for event_name in events: + machine.send(event_name) + else: + machine = smclass + + graph = DotGraphMachine(machine).get_graph() out_extension = out.rsplit(".", 1)[1] graph.write(out, format=out_extension) @@ -165,6 +175,11 @@ def main(argv=None): "out", help="File to generate the image using extension as the output format.", ) + parser.add_argument( + "--events", + nargs="+", + help="Instantiate the machine and send these events before rendering.", + ) args = parser.parse_args(argv) - write_image(qualname=args.class_path, out=args.out) + write_image(qualname=args.class_path, out=args.out, events=args.events) diff --git a/tests/test_contrib_diagram.py b/tests/test_contrib_diagram.py index 159d4a50..3d3a8152 100644 --- a/tests/test_contrib_diagram.py +++ b/tests/test_contrib_diagram.py @@ -136,6 +136,24 @@ def test_generate_complain_about_bad_sm_path(self, capsys, tmp_path): ] ) + def test_generate_image_with_events(self, tmp_path): + """CLI --events instantiates the machine and sends events before rendering.""" + out = tmp_path / "sm.png" + + main( + [ + "tests.examples.traffic_light_machine.TrafficLightMachine", + str(out), + "--events", + "cycle", + "cycle", + "cycle", + ] + ) + + assert out.exists() + assert out.stat().st_size > 0 + def test_generate_complain_about_module_without_sm(self, tmp_path): out = tmp_path / "sm.svg"