smcat turns your text into state charts
Makes this
from this
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;
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. )
A no-frills interpreter on line: sverweij.gitlab.io/state-machine-cat.
There's an Atom package
with syntax highlighting, a previewer and some export options. You can install
it from within Atom (search for state machine cat in the install section
of the settings screen) or use apm i state-machine-cat-preview
if you're a command line person.
OTOH. if you're a command line person the command line interface might be something for you too:
Just npm install --global state-machine-cat
and run smcat
This is what smcat --help
would get you:
Usage: smcat [options] [infile]
Options:
-h, --help output usage information
-V, --version output the version number
-T --output-type <type> smcat|dot|json|ast|svg. Default: svg
-I --input-type <type> smcat|json. Default: smcat
-E --engine <type> dot|circo|fdp|neato|osage|twopi. Default: dot
-i --input-from <file> File to read from. use - for stdin.
-o --output-to <file> File to write to. use - for stdout.
-l --license Display license and exit
... so to convert the above chart to sample.svg
bin/smcat doc/sample.smcat
Or, if you'd rather have the native GraphViz dot do that for you:
bin/smcat -T dot doc/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.
After you npm i
'd state-machine-cat
:
const smcat = require("state-machine-cat");
smcat.render(
`
initial => backlog;
backlog => doing;
doing => test;
`,
{
outputType: "svg"
},
(pError, pSuccess) => console.log(pError || pSuccess)
);
on => off;
- smcat automatically declares the states. You can explicitly declare them if you want them to have more than a name only - see explicit state declarations below.
on => off: switch;
UML prescribes to place conditions after events, to place
conditions within squares and to place actions
after a /
: on => off: switch flicked [not an emergency]/ light off;
.
You're free to do so, but smcat doesn't check for it. It might take the notation into account somewhere in the future (although I see no reason to make it mandatory).
on => off: switch flicked/
light off;
off => on: switch flicked/
light on;
# this is a note
on => off;
# yep, notes get rendered here as well
# multiple notes translate into multiple
# lines in notes in the diagram
doing: pick up
...;
When you name a state initial
or final
, smcat treats them as
the UML 'pseudo states' for inital and final:
initial => todo;
todo => doing;
doing => done;
done => final;
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.
^fraud?: transaction fraudulent?;
initial -> reserved;
reserved -> quoted:
quote
requested;
quoted -> ^fraud?: payment;
^fraud? -> ticketed: [no];
^fraud? -> removed: [yes];
ticketed -> final;
removed -> final;
In UML you can fork state transitions into multiple or join them into one
with the fork and join pseudo states. Both of them are represented by
a black bar. To make a join or fork pseudo state, start its name with a ]
.
Here's an example of a join:
a => ]join;
b => ]join;
]join => c;
- 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";
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" {
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;
I made the parser with pegjs - you can find it at src/parse/peg/smcat-parser.pegjs