Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/sverweij/state-machine-cat

write beautiful state charts :scream_cat:
https://github.com/sverweij/state-machine-cat

hacktoberfest scxml state-diagram state-machine statechart uml

Last synced: 1 day ago
JSON representation

write beautiful state charts :scream_cat:

Awesome Lists containing this project

README

        

# State Machine cat

_write beautiful state charts_

[![ci](https://github.com/sverweij/state-machine-cat/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sverweij/state-machine-cat/actions/workflows/ci.yml)
[![Maintainability](https://api.codeclimate.com/v1/badges/e052b461624c043f32c7/maintainability)](https://codeclimate.com/github/sverweij/state-machine-cat/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/e052b461624c043f32c7/test_coverage)](https://codeclimate.com/github/sverweij/state-machine-cat/test_coverage)
[![npm stable version](https://img.shields.io/npm/v/state-machine-cat.svg?logo=npm)](https://npmjs.com/package/state-machine-cat)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![state-machine-cat.js.org](https://img.shields.io/badge/js.org-state--machine--cat-ffb400.svg?style=flat-squared)](https://state-machine-cat.js.org)

## What?

Makes this

docs/pics/sample.png

from this

```smcat
initial,
doing: entry/ write unit test
do/ write code
exit/ ...,
# smcat recognizes initial
# and final states by name
# and renders them appropriately
final;

initial => "on backlog" : item adds most value;
"on backlog" => doing : working on it;
doing => testing : built & unit tested;
testing => "on backlog" : test not ok;
testing => final : test ok;
```

## Why

To enable me to make state charts ...

- ... that look _good_
- ... with the least effort possible
- ... whithout having to interact with drag and drop tools. Entering text
is fine, doing my own layout is not.
- ... without having to dive into GraphViz `dot` each time. GraphViz is cool,
but is was not designed to write & maintain conceptual documents in
(_You'll know what I'm talking about if you ever tried to get it to draw nested nodes. Or edges between those._ )

## Usage

### On line

A no-frills interpreter on line: [state-machine-cat.js.org](https://state-machine-cat.js.org).

### Command line interface

Just `npm install --global state-machine-cat` and run `smcat`

This is what `smcat --help` would get you:

```
Usage: smcat [options] [infile]

Write beautiful state charts - https://github.com/sverweij/state-machine-cat

Options:
-V, --version output the version number
-T --output-type svg|eps|ps|ps2|dot|smcat|json|ast|scxml|oldsvg|scjson|pdf|png (default: "svg")
-I --input-type smcat|json|scxml (default: "smcat")
-E --engine dot|circo|fdp|neato|osage|twopi (default: "dot")
-d --direction top-down|bottom-top|left-right|right-left (default: "top-down")
-o --output-to File to write to. use - for stdout.
--desugar transform pseudo states into transitions (!experimental!)
-l --license Display license and exit
-h, --help display help for command
```

... so to convert the above chart to `sample.svg`

```sh
smcat docs/sample.smcat
```

Or, if you'd rather pull dot output through GraphViz dot yourself:

```sh
smcat -T dot docs/sample.smcat -o - | dot -T svg -odoc/sample.svg
```

Leaving the options at the default settings usually deliver the best
results already, so if they bewilder you: don't worry.

When you pass the `--desugar` (⨻ experimental) switch, state-machine-cat will,
before rendering, transform some pseudo states into transitions - see
[de-sugaring state machines](docs/desugar.md) for details.

In addition to what's documented in the `--help` you can use the following 'advanced'
options:

```
--dot-graph-attrs graph attributes to pass to the dot render engine
--dot-node-attrs node attributes to pass to the dot render engine
--dot-edge-attrs edge attributes to pass to the dot render engine
```

With these you can override default attributes in the generated picture; e.g. to
get a transparent background and draw edges as line segments instead of
splines, use this:

```sh
smcat --dot-graph-attrs "bgcolor=transparent splines=line" docs/sample.smcat
```

### Syntax highlighting

- For editors supporting tree sitter (like the now defunct atom): there's [tree-sitter-smcat](https://github.com/sverweij/tree-sitter-smcat)
- For vim there's [ambagasdowa/vim-syntax-smcat](https://github.com/ambagasdowa/vim-syntax-smcat/)

### State chart XML (SCXML)

_state machine cat_ can write **and read** valid core constructs
[scxml](https://www.w3.org/TR/scxml/) documents. If you're into that sort
of thing you can read all about it in [State Machine Cat and SCXML](./docs/SCXML.md).

### Programmatically

After you `npm i` 'd `state-machine-cat`:

```javascript
import smcat from "state-machine-cat";

try {
const lSVGInAString = smcat.render(
`
initial => backlog;
backlog => doing;
doing => test;
`,
{
outputType: "svg",
},
);
console.log(lSVGInAString);
} catch (pError) {
console.error(pError);
}
```

Read more in [docs/api.md](docs/api.md)

There's also a script available to embed state machines into html with script
tags like ``. Documentation for that
'in page' feature resides over at [state-machine-cat.js.org/inpage.html](https://state-machine-cat.js.org/inpage.html)

## The language

### Short tutorial

#### simplest

```smcat
on => off;
```

rendition

- _smcat_ automatically declares the states. You _can_ explicitly declare
them if you want them to have more than a name only - see _state
declarations_ below.

#### labels

```smcat
on => off: switch;
```

rendition

UML prescribes to place _conditions_ after _events_, to place
_conditions_ within squares and to place _actions_
after a `/`: `from => to: event [conditions]/ actions`, e.g. `on => off: switch flicked [not an emergency]/ light off;`.

You're free to do so, but _smcat_ doesn't check for it. It internally takes
the notation into account, though and if you choose to export to json, scxml
or scjson you'll see them nicely split out.

```smcat
on => off: switch flicked/
light off;
off => on: switch flicked/
light on;
```

rendition

> You note that smcat rendered the states in this chart _top down_ instead of
> _left to right_. It did that because we told it so. You can do that too
> with `--direction` on the command line

#### notes

```smcat
# this is a note
on => off;
```

rendition

#### state declarations

If you need to define activities (e.g. `entry` or `exit` triggers) on
a state, you can explicitly declare the state and put the activites
after a colon:

```smcat
# yep, notes get rendered here as well
# multiple notes translate into multiple
# lines in notes in the diagram
doing:
entry/ make a feature branch
exit/ deploy code on production
...;
```

rendition

_smcat_ recognizes the `entry/` and `exit/` keywords and treats
everything after it on the same line to be the 'body' of the
trigger.

Here too: you're free to use them, but you don't _have_ to.
_smcat_ takes them into account in its internal representation
and uses them in exports to json, scxml and scjson.

#### state display names

If you want to use a display names that differ from how you
name the states (e.g. if the display names are long),
you can do so by adding a label to them:

```smcat
on [label="Lamp aan"],
off [label="Lamp uit"];

off => on: on pushed;
on => off: off pushed;
```

rendition

#### `initial` and `final`

When `initial` or `final`, is part of a state's name _smcat_ treats
it as the UML 'pseudo states' `initial` and `final` respectively:

```smcat
initial => todo;
todo => doing;
doing => done;
done => final;
```

rendition

#### `history`

_smcat_ recognizes states with `history` in their name as history states:

```smcat
initial,
"power off",
running {
running.history;

washing -> rinsing: 20 min;
rinsing -> spinning: 20 min;
spinning -> final: 20 min;
},
final;

initial => washing;
running => "power off": power out;
"power off" => running.history: restore power;
```

rendition

History states are _shallow_ by default. If you want a history state to
be _deep_ just put that somewhere in the name (e.g. `"running deep history"`
or `running.history.deep`) - and smcat will render them as such.

#### Choice - `^`

_smcat_ treats states starting with `^` as UML pseudo state _choice_. Strictly
speaking 'choice' is a superfluous element of the UML state machine
specification, but it is there and sometimes it makes diagrams easier to read.

```smcat
^fraud?: transaction fraudulent?;

initial -> reserved;
reserved -> quoted:
quote
requested;
quoted -> ^fraud?: payment;
^fraud? -> ticketed: [no];
^fraud? -> removed: [yes];
ticketed -> final;
removed -> final;
```

rendition

#### Forks, joins and junctions - `]`

In UML you can fork state transitions into multiple or join them into one
with the _fork_ (one to many) _join_ (many to one) and _junction_ (many to many)
pseudo states. _Fork_ and _join_ are represented by a black bar, _junction_
by a filled circle.
To make a _join_, _fork_ or _junction_ pseudo state, start its
name with a `]`.
Here's an example of a _join_:

```smcat
a => ]join;
b => ]join;
]join => c;
```

rendition

> State machine cat automatically derives which of the three types
> you meant by counting the number of incoming and the number of
> outgoing connections:
>
> - one incoming and multiple outgoing: it's a _fork_
> - multiple incoming and one outgoing: it's a _join_
> - all other cases: it's a _junction_
>
> If you want to defy UML semantics you can do that with
> [explicit type overrides](#overriding-the-type-of-a-state) .

```smcat
a => ]junction;
b => ]junction;
]junction => c;
]junction => d;
```

rendition

#### Terminate

UML has a special pseudo state to indicate your state machine didn't
exit properly: _terminate_. If you want to use it, declare it
explicitly:

```smcat
Aahnohd [type=terminate label="Terminated"];

a => Aahnohd: [hit by meteorite];
```

rendition

> For proper exits you'd typically use the _final_ state.

#### Gotchas

- when you need `;`, `,`, `{`, `[` or spaces as part of a state - place em in quotes
`"a state"`
- Activities have the same restriction, except they allow spaces.
- Labels have the same restriction as activities, except they allow for `,` too.
- State declaration precedence is: deep wins from shallow; explicit wins from
implicit
- It's possible to declare the same state multiple times on the same level, buts
smcat will take the last declaration into account only. For example:

This

```
# first declaration of "cool state"
"cool state",
"other state",
# second declaration of "cool state"
"cool state": cool down;
```

results in (/ is equivalent to):

```
# second declaration of "cool state"
"cool state": cool down,
"other state";
```

#### nested state machines

It's possible to have state machines _within_ states.
the states _stopped_, _playing_ and _pause_ can only occur when
the tape player is on:

```
initial,
"tape player off",
"tape player on":
entry/ LED on
exit/ LED off
{
stopped, playing, paused;

stopped => playing : play;
playing => stopped : stop;
playing => paused : pause;
paused => playing : pause;
paused => stopped : stop;
};

initial => "tape player off";
"tape player off" => stopped : power;
"tape player on" => "tape player off" : power;

```

As you can see in this sample you can use activities (like entry and exit
triggers) in the composite state declaration, just as you'd do for state that
does not contain a state machine.

tape player rendition

#### parallel states

If stuff is happening in parallel within a state you can express
that too. Just make sure the state has the word "parallel" in there:

```smcat
initial,
bla.parallel {
first{
first.thing -> first.thang;
},
second{
second.thing -> second.thang;
second.thang -> second.thing;
};
},
final;

initial -> ]split;
]split -> first.thing;
]split -> second.thing;
first.thang -> ]merge;
second.thang -> ]merge;
]merge -> final;
```

parallel

#### `internal` and `external` transitions

If you need to mark a transition in a nested state machine as either _internal_
or _external_ - use the `type` attribute. The default type for a transition
is `external` - just like it is in SCXML.

```smcat
playing {
resting => walking;
walking => resting;

};

playing => playing: ingest food;
playing => playing [type=internal]: ingest drink;
```

internal transition

#### marking states _active_

You can mark one or more states as active by adding `active` as an
attribute for that state. E.g. to make the `do` state an active one
in the demming circle, do this:

```smcat
do [active];

initial -> plan;
plan -> do;
do -> study;
study -> act;
act -> plan;
```

which will result in

active states

#### colors and line width

As of version 4.2.0 state-machine-cat has (experimental) support for colors on both
states and transitions and from version 8.1.0 for width on transitions.

For example, this ...

```smcat
eat [color="#008800"],
sleep [color="blue" active],
meow [color="red"],
play [color="purple"];

sleep -> meow [color="red"] : wake up;
meow -> meow [color="red"] : no response from human;
meow -> eat [color="#008800"] : human gives food;
meow -> play [color="purple"] : human gives toy;
play -> sleep [color="blue"] : tired or bored;
eat -> sleep [color="blue" width=3.5] : belly full;
```

... would yield this diagram:

colored states and transitions

What does 'experimental' mean?

> The color attribute is probably here to stay, as will the width
>
> However, I haven't found the balance between ease of use
> and expressiveness yet. Should the text in a state be rendered in the same color?
> should the background color automatically be rendered as well? In the same color,
> or in a shade smartly derived? Or should I include a bunch of color attributes
> (e.g. fillcolor, textcolor, textbgcolor) for ultimate control?

#### classes

As of version 7.4.0 you can use the keyword `class` as an extended keyword on
both states and transitions. When you render `svg` or `dot` you'll see what you
entered there in the output in the `class` attributes of their respective
elements, along with the type of element (either 'state' or 'transition') and
optionally the type of state or transtion (e.g. for state: 'initial', 'regular',
'final' etc.).

For example, this ...

```smcat
a [class="dismissed"],
b [class="y"];

a => b [class="a bunch of classes"];
```

... will yield this 'dot' program ...

```graphviz
digraph "state transitions" {
fontname="Helvetica" fontsize=12 penwidth=2.0 splines=true ordering=out compound=true overlap=scale nodesep=0.3 ranksep=0.1
node [shape=plaintext style=filled fillcolor="#FFFFFF01" fontname=Helvetica fontsize=12 penwidth=2.0]
edge [fontname=Helvetica fontsize=10]

"a" [margin=0 class="state regular dismissed" label= <

a

>]
"b" [margin=0 class="state regular y" label= <

b

>]

"a" -> "b" [label=" " class="transition a bunch of classes"]
}
```

Which will pass the class attributes on to the svg like so. E.g. the svg snippet
for the `a` state will look like this:

```svg


a

a

```

#### Gotchas

- You will have to provide the style sheet defining the classes yourself in the
context where you render the svg in order for them to actually show up
- The characters you can use for class names is limited to alpha-numerics, dashes,
underscores - and spaces to separate them. This to make it harder to use
state-machine-cat to construct svg's that are either invalid or malicious. The
limited character set is in contrast to what css allows, which is
[everything under the sun and then some](https://mathiasbynens.be/notes/css-escapes) -
but it seems like a reasonable compromise.

### overriding the type of a state

As you read above, _state machine cat_ derives the type of a state from its name.
In some cases that might not be what you want. In those cases, you can
override the type with the `type` attribute, like in this example for the
`initialized` state.

```smcat
initial,
starting,
initialized [color="red" type=regular],
running,
final;

initial => starting;
starting => initialized;
initialized => running;
initialized => stopped;
running => stopped;
stopped => final;
```

colored states and transitions

The values you can use for the `type` of a state:

| type | example |
| ------------- | ------------------------------------------------------------------------------------ |
| `regular` | regular state |
| `initial` | initial state |
| `final` | final state |
| `history` | history state |
| `deephistory` | deep history state |
| `choice` | choice state |
| `fork` | fork state |
| `join` | join state |
| `forkjoin` | forkjoin state |
| `junction` | junction state |
| `parallel` | parallel state |
| `terminate` | terminate state |

#### grammar

I made the parser with peggy - you can find it at
[src/parse/peg/smcat-parser.peggy](src/parse/smcat/peg/smcat-parser.peggy), and
railroad diagrams generated from these on [state-machine-cat.js.org/grammar.html](https://state-machine-cat.js.org/grammar.html)

## Status

- Thoroughly tested and good enough for public use.
- Despite this you might bump into the occasional issue - don't hesitate to
report it on [GitHub](https://github.com/sverweij/state-machine-cat/issues).
- Runs on latest versions of firefox, safari and chrome and node versions >= 18.17.0
Although it might run on other environments, it's not tested there. I will
reject issues on these other environments, unless they're accompanied with
sufficient enticement to make it worth my while.

colored states and transitions