state-machine-generator

state-machine-generator.

Usage no npm install needed!

<script type="module">
  import stateMachineGenerator from 'https://cdn.skypack.dev/state-machine-generator';
</script>

README

state-machine-generator

Code generator for state machines.

Supports:

  • java - a Java 8 implementation.
  • ts - a TypeScript implementation.
  • python - a Python implementation.
  • dot - a GraphViz dot file that documents the state machine.
  • asciidoctor - an asciidoctor file that documents the state machine.
  • local templates - just point to the folder.

Sample Test Machine Definition

Save it as awesome.yml.

name: Test
package: com.ciplogic.awesome
states:
  - DEFAULT
  - RUNNING
  - STOPPED
transitions:
  DEFAULT:
    run: RUNNING
    stop: STOPPED
  RUNNING:
    pause: DEFAULT
    stop: STOPPED

Generating the State Machine Code

state-machine-generator java awesome.yml

This will generate a TestStateMachine that can transition across states from TestState.

Notes

The state machine generated is thread safe.

User Templates

name

Xyz will be replaced to the state machine name from the yml file.

package

com.ciplogic.statemachine will be replaced to the package name from the yml file.

states

States will be replaced using the template after the BEGIN_STATES:, with one entry per line, with STATE_NAME replaced as the actual value. What's between BEGIN and END will be replaced.

For example this:

public enum XyzState {
    // BEGIN_STATES: STATE_NAME,
    EXISTING,
    NOT_EXISTING,
    // END_STATES
}

will be replaced in the case of awesome.yml to:

public enum XyzState {
DEFAULT,
RUNNING,
STOPPED,
}

transitions

The same way as the states, transitions will be replaced using this template:

// BEGIN_TRANSITIONS: this.registerTransition("TRANSITION_NAME", XyzState.FROM_STATE, XyzState.TO_STATE);
...
// END_TRANSITIONS

Of course, in here the TRANSITION_NAME, FROM_STATE and TO_STATE will be replaced using the values from the transitions sections from the yaml.

Transitions can also be accessed as only the set of names, for example to generate methods that will do said transitions:

// BEGIN_TRANSITION_SET: public XyzState TRANSITION_NAME() { return this.transition("TRANSITION_NAME"); }
...
// END_TRANSITION_SET
// BEGIN_TRANSITION_SET: public XyzState TRANSITION_NAME(Object data) { return this.transition("TRANSITION_NAME", data); }
...
// END_TRANSITION_SET

Handlebars Support

From version 2.0.0, it is also possible to have a handlebars template in.

// BEGIN_HANDLEBARS
// {{#each transitions}}
// System.out.println("{{startState}} --{{name}}--> {{endState}}");
// {{/each}}
System.out.println("STOPPED --run--> RUNNING");
// END_HANDLEBARS

The code that is not commented in the section will be removed from the output. Thus you can create unit tests for the actual implementations of the state machines, and test them aginst the template itself.

From version 2.2.0, it is also possible to have # prefixing handlebars templates.

#BEGIN_HANDLEBARS
#{{#each transitions}}
#print('{{startState}} --{{name}}--> {{endState}}')
#{{/each}}
print('STOPPED --run--> RUNNING')
#END_HANDLEBARS

Each line from the handlebars template must be prefixed by a comment.

Model

The model data that is going to be sent to the template implements the StateModel interface shown below. All the properties in the yml file are copied in the model that will be passed to the template.

type State = string;

export interface Transition {
    name: string
    startState: State
    endState: State
}

export interface StateModel {
  name: string
  package: string
  states: Array<State>
  transitions: Array<Transition>
  transitionSet: Array<string>
}

capitalize Helper

This helper is passed into the handlebars engine, and capitalizes the string.