state-machine-cat 12.0.17 → 12.0.19
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.
- package/README.md +1 -1
- package/dist/cli/cli.mjs +2 -17
- package/dist/index.mjs +5 -4
- package/dist/options.mjs +0 -1
- package/dist/parse/scxml/index.mjs +1 -1
- package/dist/render/dot/attributebuilder.mjs +20 -14
- package/dist/render/dot/index.mjs +263 -142
- package/dist/render/dot/utl.mjs +83 -11
- package/dist/render/index-node.mjs +1 -2
- package/dist/state-machine-model.mjs +19 -12
- package/dist/version.mjs +1 -1
- package/package.json +2 -8
- package/dist/render/dot/dot.states.template.cjs +0 -1
- package/dist/render/dot/dot.template.cjs +0 -1
- package/dist/render/dot/render-dot-from-ast.mjs +0 -44
- package/dist/render/dot/state-transformers.mjs +0 -119
- package/dist/render/dot/transition-transformers.mjs +0 -45
- package/dist/render/ndot/attributebuilder.mjs +0 -60
- package/dist/render/ndot/index.mjs +0 -243
- package/dist/render/ndot/utl.mjs +0 -77
package/README.md
CHANGED
|
@@ -518,7 +518,7 @@ eat -> sleep [color="blue" width=3.5] : belly full;
|
|
|
518
518
|
|
|
519
519
|
... would yield this diagram:
|
|
520
520
|
|
|
521
|
-
<img width="
|
|
521
|
+
<img width="488" alt="colored states and transitions" src="https://raw.githubusercontent.com/sverweij/state-machine-cat/main/docs/pics/10colored_states_and_transitions.png">
|
|
522
522
|
|
|
523
523
|
What does 'experimental' mean?
|
|
524
524
|
|
package/dist/cli/cli.mjs
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { parseArgs } from "node:util";
|
|
3
|
-
import
|
|
2
|
+
import { version } from "../version.mjs";
|
|
4
3
|
import { formatError, displayLicense, transform } from "./actions.mjs";
|
|
5
4
|
import normalize from "./normalize.mjs";
|
|
6
5
|
import validations from "./validations.mjs";
|
|
7
|
-
const $package = JSON.parse(
|
|
8
|
-
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
9
|
-
);
|
|
10
6
|
const HELP_TEXT = `Usage: smcat [options] [infile]
|
|
11
7
|
|
|
12
8
|
Write beautiful state charts - https://github.com/sverweij/state-machine-cat
|
|
@@ -130,31 +126,20 @@ function parseArguments(pArguments) {
|
|
|
130
126
|
);
|
|
131
127
|
return { values: camelizeObject(values), positionals };
|
|
132
128
|
}
|
|
133
|
-
function assertNodeVersion(pCurrentNodeVersion, pSupportedEngines) {
|
|
134
|
-
if (!satisfies(pCurrentNodeVersion, pSupportedEngines)) {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`\nERROR: your node version (${pCurrentNodeVersion}) is not recent enough.\n` +
|
|
137
|
-
` state-machine-cat is supported on node ${pSupportedEngines}\n\n`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
129
|
export default async function cli(pArguments = process.argv, pOptions) {
|
|
142
130
|
const lOptions = {
|
|
143
|
-
currentNodeVersion: process.versions.node,
|
|
144
|
-
supportedEngines: $package.engines.node,
|
|
145
131
|
outStream: process.stdout,
|
|
146
132
|
errorStream: process.stderr,
|
|
147
133
|
...pOptions,
|
|
148
134
|
};
|
|
149
135
|
try {
|
|
150
|
-
assertNodeVersion(lOptions.currentNodeVersion, lOptions.supportedEngines);
|
|
151
136
|
const { values, positionals } = parseArguments(pArguments.slice(2));
|
|
152
137
|
if (values.help) {
|
|
153
138
|
lOptions.outStream.write(HELP_TEXT, "utf8");
|
|
154
139
|
return;
|
|
155
140
|
}
|
|
156
141
|
if (values.version) {
|
|
157
|
-
lOptions.outStream.write(`${
|
|
142
|
+
lOptions.outStream.write(`${version}\n`, "utf8");
|
|
158
143
|
return;
|
|
159
144
|
}
|
|
160
145
|
if (values.license) {
|
package/dist/index.mjs
CHANGED
|
@@ -4,11 +4,12 @@ import desugar from "./transform/desugar.mjs";
|
|
|
4
4
|
import getRenderFunction from "./render/index.mjs";
|
|
5
5
|
import { version as _version } from "./version.mjs";
|
|
6
6
|
export function render(pScript, pOptions) {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
const lOptions = pOptions ?? {};
|
|
8
|
+
const lStateMachine = parse.getAST(pScript, lOptions);
|
|
9
|
+
const lDesugar = options.getOptionValue(lOptions, "desugar");
|
|
10
|
+
return getRenderFunction(options.getOptionValue(lOptions, "outputType"))(
|
|
10
11
|
lDesugar ? desugar(lStateMachine) : lStateMachine,
|
|
11
|
-
|
|
12
|
+
lOptions,
|
|
12
13
|
);
|
|
13
14
|
}
|
|
14
15
|
export const version = _version;
|
package/dist/options.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fastxml from "fast-xml-parser";
|
|
2
2
|
import he from "he";
|
|
3
|
-
import traverse from "
|
|
3
|
+
import traverse from "neotraverse";
|
|
4
4
|
import utl from "../../transform/utl.mjs";
|
|
5
5
|
import parserHelpers from "../parser-helpers.mjs";
|
|
6
6
|
import { castArray } from "./utl.mjs";
|
|
@@ -36,19 +36,25 @@ const EDGE_ATTRIBUTES = [
|
|
|
36
36
|
function toNameValueString(pAttribute) {
|
|
37
37
|
return `${pAttribute.name}=${pAttribute.value}`;
|
|
38
38
|
}
|
|
39
|
+
export function buildGraphAttributes(pEngine, pDirection, pDotGraphAttributes) {
|
|
40
|
+
return GENERIC_GRAPH_ATTRIBUTES.concat(GRAPH_ATTRIBUTES[pEngine] || [])
|
|
41
|
+
.concat(DIRECTION_ATTRIBUTES[pDirection] || [])
|
|
42
|
+
.concat(pDotGraphAttributes || [])
|
|
43
|
+
.map(toNameValueString)
|
|
44
|
+
.join(" ");
|
|
45
|
+
}
|
|
46
|
+
export function buildNodeAttributes(pDotNodeAttributes) {
|
|
47
|
+
return NODE_ATTRIBUTES.concat(pDotNodeAttributes || [])
|
|
48
|
+
.map(toNameValueString)
|
|
49
|
+
.join(" ");
|
|
50
|
+
}
|
|
51
|
+
export function buildEdgeAttributes(pDotEdgeAttributes) {
|
|
52
|
+
return EDGE_ATTRIBUTES.concat(pDotEdgeAttributes || [])
|
|
53
|
+
.map(toNameValueString)
|
|
54
|
+
.join(" ");
|
|
55
|
+
}
|
|
39
56
|
export default {
|
|
40
|
-
buildGraphAttributes
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.concat(pDotGraphAttributes || [])
|
|
44
|
-
.map(toNameValueString)
|
|
45
|
-
.join(" "),
|
|
46
|
-
buildNodeAttributes: (pDotNodeAttributes) =>
|
|
47
|
-
NODE_ATTRIBUTES.concat(pDotNodeAttributes || [])
|
|
48
|
-
.map(toNameValueString)
|
|
49
|
-
.join(" "),
|
|
50
|
-
buildEdgeAttributes: (pDotEdgeAttributes) =>
|
|
51
|
-
EDGE_ATTRIBUTES.concat(pDotEdgeAttributes || [])
|
|
52
|
-
.map(toNameValueString)
|
|
53
|
-
.join(" "),
|
|
57
|
+
buildGraphAttributes,
|
|
58
|
+
buildNodeAttributes,
|
|
59
|
+
buildEdgeAttributes,
|
|
54
60
|
};
|
|
@@ -1,150 +1,271 @@
|
|
|
1
|
-
import
|
|
1
|
+
import he from "he";
|
|
2
|
+
import { getOptionValue } from "../../options.mjs";
|
|
2
3
|
import StateMachineModel from "../../state-machine-model.mjs";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
) {
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
4
|
+
import {
|
|
5
|
+
buildGraphAttributes,
|
|
6
|
+
buildNodeAttributes,
|
|
7
|
+
buildEdgeAttributes,
|
|
8
|
+
} from "./attributebuilder.mjs";
|
|
9
|
+
import {
|
|
10
|
+
escapeLabelString,
|
|
11
|
+
formatActionType,
|
|
12
|
+
getTransitionPorts,
|
|
13
|
+
isCompositeSelf,
|
|
14
|
+
isVertical,
|
|
15
|
+
noteToLabel,
|
|
16
|
+
normalizeState,
|
|
17
|
+
stateNote,
|
|
18
|
+
} from "./utl.mjs";
|
|
19
|
+
let gRenderedTransitions = new Set();
|
|
20
|
+
function initial(pState, pIndent) {
|
|
21
|
+
const lActiveAttribute = pState.active ? " penwidth=3.0" : "";
|
|
22
|
+
return `${pIndent} "${pState.name}" [shape=circle style=filled class="${pState.class}" color="${pState.color}" fillcolor="${pState.color}" fixedsize=true height=0.15 label=""${lActiveAttribute}]${pState.noteText}`;
|
|
23
|
+
}
|
|
24
|
+
function regularStateActions(pActions, pIndent) {
|
|
25
|
+
return pActions
|
|
26
|
+
.map((pAction) =>
|
|
27
|
+
he.escape(`${formatActionType(pAction.type)}${pAction.body}`),
|
|
28
|
+
)
|
|
29
|
+
.map((pActionString, pIndex) => {
|
|
30
|
+
let lReturnValue = `<tr><td align="left" cellpadding="2">${pActionString}</td></tr>`;
|
|
31
|
+
if (pIndex === 0) {
|
|
32
|
+
lReturnValue = `<hr/>${lReturnValue}`;
|
|
33
|
+
}
|
|
34
|
+
return `\n${pIndent} ${lReturnValue}`;
|
|
35
|
+
})
|
|
36
|
+
.join("");
|
|
37
|
+
}
|
|
38
|
+
function compositeStateActions(pActions, pIndent) {
|
|
39
|
+
return pActions
|
|
40
|
+
.map((pAction) =>
|
|
41
|
+
he.escape(`${formatActionType(pAction.type)}${pAction.body}`),
|
|
42
|
+
)
|
|
43
|
+
.map((pActionString, pIndex) => {
|
|
44
|
+
let lReturnValue = `<tr><td align="left">${pActionString}</td></tr>`;
|
|
45
|
+
if (pIndex === 0) {
|
|
46
|
+
lReturnValue = `<hr/>${lReturnValue}`;
|
|
47
|
+
}
|
|
48
|
+
return `\n${pIndent} ${lReturnValue}`;
|
|
49
|
+
})
|
|
50
|
+
.join("");
|
|
51
|
+
}
|
|
52
|
+
function atomicRegular(pState, pIndent) {
|
|
53
|
+
const lActiveAttribute = pState.active ? " peripheries=1 style=rounded" : "";
|
|
54
|
+
const lCellPadding = (pState.actions?.length ?? 0) > 0 ? 2 : 7;
|
|
55
|
+
const lActions = regularStateActions(pState?.actions ?? [], pIndent);
|
|
56
|
+
const lLabel = pState.active ? `<i>${pState.label}</i>` : pState.label;
|
|
57
|
+
const lLabelTag = `
|
|
58
|
+
${pIndent} <table align="center" cellborder="0" border="2" style="rounded" width="48">
|
|
59
|
+
${pIndent} <tr><td width="48" cellpadding="${lCellPadding}">${lLabel}</td></tr>${lActions}
|
|
60
|
+
${pIndent} </table>`;
|
|
61
|
+
return `${pIndent} "${pState.name}" [margin=0 class="${pState.class}" label= <${lLabelTag}
|
|
62
|
+
${pIndent} >${pState.colorAttribute}${pState.fontColorAttribute}${lActiveAttribute}]${pState.noteText}`;
|
|
63
|
+
}
|
|
64
|
+
function compositeRegular(pState, pIndent, pOptions, pModel) {
|
|
65
|
+
const lPenWidth = pState.isParallelArea
|
|
66
|
+
? "1.0"
|
|
67
|
+
: pState.active
|
|
68
|
+
? "3.0"
|
|
69
|
+
: "2.0";
|
|
70
|
+
const lStyle = pState.isParallelArea ? "dashed" : "rounded";
|
|
71
|
+
const lActions = compositeStateActions(pState?.actions ?? [], pIndent);
|
|
72
|
+
const lLabel = pState.active ? `<i>${pState.label}</i>` : pState.label;
|
|
73
|
+
const lLabelTag = `${pIndent} <table cellborder="0" border="0">
|
|
74
|
+
${pIndent} <tr><td>${lLabel}</td></tr>${lActions}
|
|
75
|
+
${pIndent} </table>`;
|
|
76
|
+
const lSelfTransitionHelperPoints = pModel
|
|
77
|
+
.findExternalSelfTransitions(pState.name)
|
|
78
|
+
.map(
|
|
79
|
+
(pTransition) =>
|
|
80
|
+
`${pIndent} "self_tr_${pTransition.from}_${pTransition.to}_${pTransition.id}" [shape=point style=invis width=0 height=0 fixedsize=true]\n`,
|
|
81
|
+
)
|
|
82
|
+
.join("");
|
|
83
|
+
return `${lSelfTransitionHelperPoints}${pIndent} subgraph "cluster_${pState.name}" {
|
|
84
|
+
${pIndent} class="${pState.class}" label= <
|
|
85
|
+
${lLabelTag}
|
|
86
|
+
${pIndent} > style=${lStyle} penwidth=${lPenWidth}${pState.colorAttribute}${pState.fontColorAttribute}
|
|
87
|
+
${pIndent} "${pState.name}" [shape=point style=invis margin=0 width=0 height=0 fixedsize=true]
|
|
88
|
+
${states(pState?.statemachine?.states ?? [], `${pIndent} `, pOptions, pModel)}
|
|
89
|
+
${pIndent} }${pState.noteText}`;
|
|
90
|
+
}
|
|
91
|
+
function regular(pState, pIndent, pOptions, pModel) {
|
|
92
|
+
if (pState.statemachine) {
|
|
93
|
+
return compositeRegular(pState, pIndent, pOptions, pModel);
|
|
94
|
+
}
|
|
95
|
+
return atomicRegular(pState, pIndent);
|
|
96
|
+
}
|
|
97
|
+
function history(pState, pIndent) {
|
|
98
|
+
const lActiveAttribute = pState.active ? " peripheries=2 penwidth=3.0" : "";
|
|
99
|
+
return `${pIndent} "${pState.name}" [shape=circle class="${pState.class}" label="H"${pState.colorAttribute}${pState.fontColorAttribute}${lActiveAttribute}]${pState.noteText}`;
|
|
100
|
+
}
|
|
101
|
+
function deepHistory(pState, pIndent) {
|
|
102
|
+
const lActiveAttribute = pState.active ? " peripheries=2 penwidth=3.0" : "";
|
|
103
|
+
return `${pIndent} "${pState.name}" [shape=circle class="${pState.class}" label="H*"${pState.colorAttribute}${pState.fontColorAttribute}${lActiveAttribute}]${pState.noteText}`;
|
|
104
|
+
}
|
|
105
|
+
function choiceActions(pActions, pActive) {
|
|
106
|
+
return pActions
|
|
107
|
+
.map((pAction) => {
|
|
108
|
+
let lReturnValue = he.escape(
|
|
109
|
+
`${formatActionType(pAction.type)}${pAction.body}`,
|
|
32
110
|
);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.map(stateTransformers.setLabel)
|
|
36
|
-
.map(stateTransformers.nameNote)
|
|
37
|
-
.map(stateTransformers.classifyState)
|
|
38
|
-
.map(stateTransformers.escapeStateStrings)
|
|
39
|
-
.map(stateTransformers.flattenNote)
|
|
40
|
-
.map(stateTransformers.flattenActions)
|
|
41
|
-
.map(stateTransformers.flagParallelChildren)
|
|
42
|
-
.map(stateTransformers.tipForkJoinStates(pDirection))
|
|
43
|
-
.map(stateTransformers.recolor(pNodeAttributes))
|
|
44
|
-
.map(addExternalSelfTransitions(pStateMachineModel));
|
|
45
|
-
}
|
|
46
|
-
function splitStates(pStateMachine) {
|
|
47
|
-
pStateMachine.initialStates = pStateMachine.states.filter(
|
|
48
|
-
stateTransformers.isType("initial"),
|
|
49
|
-
);
|
|
50
|
-
pStateMachine.regularStates = pStateMachine.states.filter(
|
|
51
|
-
(pState) =>
|
|
52
|
-
stateTransformers.isType("regular")(pState) && !pState.statemachine,
|
|
53
|
-
);
|
|
54
|
-
pStateMachine.historyStates = pStateMachine.states.filter(
|
|
55
|
-
stateTransformers.isType("history"),
|
|
56
|
-
);
|
|
57
|
-
pStateMachine.deepHistoryStates = pStateMachine.states.filter(
|
|
58
|
-
stateTransformers.isType("deephistory"),
|
|
59
|
-
);
|
|
60
|
-
pStateMachine.choiceStates = pStateMachine.states.filter(
|
|
61
|
-
stateTransformers.isType("choice"),
|
|
62
|
-
);
|
|
63
|
-
pStateMachine.forkjoinStates = pStateMachine.states.filter(
|
|
64
|
-
stateTransformers.isOneOfTypes(["fork", "join", "forkjoin"]),
|
|
65
|
-
);
|
|
66
|
-
pStateMachine.junctionStates = pStateMachine.states.filter(
|
|
67
|
-
stateTransformers.isType("junction"),
|
|
68
|
-
);
|
|
69
|
-
pStateMachine.terminateStates = pStateMachine.states.filter(
|
|
70
|
-
stateTransformers.isType("terminate"),
|
|
71
|
-
);
|
|
72
|
-
pStateMachine.finalStates = pStateMachine.states.filter(
|
|
73
|
-
stateTransformers.isType("final"),
|
|
74
|
-
);
|
|
75
|
-
pStateMachine.compositeStates = pStateMachine.states.filter(
|
|
76
|
-
(pState) => pState.statemachine,
|
|
77
|
-
);
|
|
78
|
-
return pStateMachine;
|
|
79
|
-
}
|
|
80
|
-
function addEndTypes(pStateMachineModel) {
|
|
81
|
-
return (pTransition) => {
|
|
82
|
-
if (pStateMachineModel.findStateByName(pTransition.from).statemachine) {
|
|
83
|
-
pTransition.fromComposite = true;
|
|
84
|
-
}
|
|
85
|
-
if (pStateMachineModel.findStateByName(pTransition.to).statemachine) {
|
|
86
|
-
pTransition.toComposite = true;
|
|
87
|
-
}
|
|
88
|
-
return pTransition;
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function addCompositeSelfFlag(pStateMachineModel) {
|
|
92
|
-
return (pTransition) => {
|
|
93
|
-
let lAdditionalAttributes = {};
|
|
94
|
-
if (utl.isCompositeSelf(pStateMachineModel, pTransition)) {
|
|
95
|
-
if (pStateMachineModel.findStateByName(pTransition.from).hasParent) {
|
|
96
|
-
lAdditionalAttributes = { hasParent: true, isCompositeSelf: true };
|
|
97
|
-
} else {
|
|
98
|
-
lAdditionalAttributes = { isCompositeSelf: true };
|
|
111
|
+
if (pActive) {
|
|
112
|
+
lReturnValue = `<i>${lReturnValue}</i>`;
|
|
99
113
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return pTransition;
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function transformTransitions(pStateMachineModel, pDirection) {
|
|
114
|
-
return pStateMachineModel.flattenedTransitions
|
|
115
|
-
.map(nameTransition())
|
|
116
|
-
.map(transitionTransformers.escapeTransitionStrings)
|
|
117
|
-
.map(transitionTransformers.classifyTransition)
|
|
118
|
-
.map(stateTransformers.flattenNote)
|
|
119
|
-
.map(addEndTypes(pStateMachineModel))
|
|
120
|
-
.map(addCompositeSelfFlag(pStateMachineModel))
|
|
121
|
-
.map(transitionTransformers.addPorts(pDirection));
|
|
122
|
-
}
|
|
123
|
-
export default (pStateMachine, pOptions) => {
|
|
124
|
-
pOptions = pOptions || {};
|
|
125
|
-
let lStateMachine = structuredClone(pStateMachine);
|
|
126
|
-
const lStateMachineModel = new StateMachineModel(lStateMachine);
|
|
127
|
-
lStateMachine.transitions = transformTransitions(
|
|
128
|
-
lStateMachineModel,
|
|
129
|
-
pOptions.direction,
|
|
114
|
+
return lReturnValue;
|
|
115
|
+
})
|
|
116
|
+
.join("\\n");
|
|
117
|
+
}
|
|
118
|
+
function choice(pState, pIndent) {
|
|
119
|
+
const lActiveAttribute = pState.active ? "penwidth=3.0 " : "";
|
|
120
|
+
const lActions = choiceActions(
|
|
121
|
+
pState?.actions ?? [],
|
|
122
|
+
pState?.active ?? false,
|
|
130
123
|
);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
const lLabelTag = lActions;
|
|
125
|
+
const lDiamond = `${pIndent} "${pState.name}" [shape=diamond fixedsize=true width=0.35 height=0.35 fontsize=10 label=" " class="${pState.class}"${pState.colorAttribute}${lActiveAttribute}]`;
|
|
126
|
+
const lLabelConstruct = `${pIndent} "${pState.name}" -> "${pState.name}" [color="#FFFFFF01" fontcolor="${pState.color}" class="${pState.class}" label=<${lLabelTag}>]`;
|
|
127
|
+
return `${lDiamond}\n${lLabelConstruct}${pState.noteText}`;
|
|
128
|
+
}
|
|
129
|
+
function forkjoin(pState, pIndent, pOptions) {
|
|
130
|
+
const lActiveAttribute = pState.active ? "penwidth=3.0 " : "";
|
|
131
|
+
const lDirection = getOptionValue(pOptions, "direction");
|
|
132
|
+
const lSizingExtras = isVertical(lDirection) ? " height=0.1" : " width=0.1";
|
|
133
|
+
return `${pIndent} "${pState.name}" [shape=rect fixedsize=true label=" " style=filled class="${pState.class}" color="${pState.color}" fillcolor="${pState.color}"${lActiveAttribute}${lSizingExtras}]${pState.noteText}`;
|
|
134
|
+
}
|
|
135
|
+
function junction(pState, pIndent) {
|
|
136
|
+
const lActiveAttribute = pState.active ? " penwidth=3.0" : "";
|
|
137
|
+
const lNote = stateNote(pState, pIndent);
|
|
138
|
+
return `${pIndent} "${pState.name}" [shape=circle fixedsize=true height=0.15 label="" style=filled class="${pState.class}" color="${pState.color}" fillcolor="${pState.color}"${lActiveAttribute}]${lNote}`;
|
|
139
|
+
}
|
|
140
|
+
function terminate(pState, pIndent) {
|
|
141
|
+
const lLabelTag = `
|
|
142
|
+
${pIndent} <table align="center" cellborder="0" border="0">
|
|
143
|
+
${pIndent} <tr><td cellpadding="0"><font color="${pState.color}" point-size="20">X</font></td></tr>
|
|
144
|
+
${pIndent} <tr><td cellpadding="0"><font color="${pState.color}">${pState.label}</font></td></tr>
|
|
145
|
+
${pIndent} </table>`;
|
|
146
|
+
return `${pIndent} "${pState.name}" [label= <${lLabelTag}
|
|
147
|
+
${pIndent} > class="${pState.class}"]${pState.noteText}`;
|
|
148
|
+
}
|
|
149
|
+
function final(pState, pIndent) {
|
|
150
|
+
const lActiveAttribute = pState.active ? " peripheries=2 penwidth=3.0" : "";
|
|
151
|
+
return `${pIndent} "${pState.name}" [shape=circle style=filled class="${pState.class}" color="${pState.color}" fillcolor="${pState.color}" fixedsize=true height=0.15 peripheries=2 label=""${lActiveAttribute}]${pState.noteText}`;
|
|
152
|
+
}
|
|
153
|
+
const STATE_TYPE2FUNCTION = new Map([
|
|
154
|
+
["initial", initial],
|
|
155
|
+
["regular", regular],
|
|
156
|
+
["history", history],
|
|
157
|
+
["deephistory", deepHistory],
|
|
158
|
+
["choice", choice],
|
|
159
|
+
["fork", forkjoin],
|
|
160
|
+
["forkjoin", forkjoin],
|
|
161
|
+
["join", forkjoin],
|
|
162
|
+
["junction", junction],
|
|
163
|
+
["terminate", terminate],
|
|
164
|
+
["final", final],
|
|
165
|
+
]);
|
|
166
|
+
function state(pState, pIndent, pOptions, pModel) {
|
|
167
|
+
const lState = normalizeState(pState, pOptions, pIndent);
|
|
168
|
+
const lCandidateTransitions = pModel.findTransitionsToSiblings(
|
|
169
|
+
pState.name,
|
|
170
|
+
gRenderedTransitions,
|
|
136
171
|
);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
172
|
+
lCandidateTransitions.forEach((pTransition) => {
|
|
173
|
+
gRenderedTransitions.add(pTransition.id);
|
|
174
|
+
});
|
|
175
|
+
const lTransitions = transitions(
|
|
176
|
+
lCandidateTransitions,
|
|
177
|
+
pIndent,
|
|
178
|
+
pOptions,
|
|
179
|
+
pModel,
|
|
142
180
|
);
|
|
143
|
-
|
|
144
|
-
|
|
181
|
+
return (
|
|
182
|
+
(STATE_TYPE2FUNCTION.get(pState.type) ?? regular)(
|
|
183
|
+
lState,
|
|
184
|
+
pIndent,
|
|
185
|
+
pOptions,
|
|
186
|
+
pModel,
|
|
187
|
+
) +
|
|
188
|
+
lTransitions +
|
|
189
|
+
"\n"
|
|
145
190
|
);
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
}
|
|
192
|
+
function states(pStates, pIndent, pOptions, pModel) {
|
|
193
|
+
return pStates
|
|
194
|
+
.map((pState) => state(pState, pIndent, pOptions, pModel))
|
|
195
|
+
.join("");
|
|
196
|
+
}
|
|
197
|
+
function transition(pTransition, pIndent, pOptions, pModel) {
|
|
198
|
+
const lLabel = `${escapeLabelString(pTransition.label ?? " ")}`;
|
|
199
|
+
const lColorAttribute = pTransition.color
|
|
200
|
+
? ` color="${pTransition.color}"`
|
|
201
|
+
: "";
|
|
202
|
+
const lFontColorAttribute = pTransition.color
|
|
203
|
+
? ` fontcolor="${pTransition.color}"`
|
|
204
|
+
: "";
|
|
205
|
+
const lPenWidth = pTransition.width ? ` penwidth=${pTransition.width}` : "";
|
|
206
|
+
const lClass = pTransition.class
|
|
207
|
+
? `transition${pTransition.type ? " " + pTransition.type + " " : " "}${pTransition.class}`
|
|
208
|
+
: `transition${pTransition.type ? " " + pTransition.type : ""}`;
|
|
209
|
+
const lTail = pModel.findStateByName(pTransition.from)?.statemachine
|
|
210
|
+
? ` ltail="cluster_${pTransition.from}"`
|
|
211
|
+
: "";
|
|
212
|
+
const lHead = pModel.findStateByName(pTransition.to)?.statemachine
|
|
213
|
+
? ` lhead="cluster_${pTransition.to}"`
|
|
214
|
+
: "";
|
|
215
|
+
const lTransitionName = `tr_${pTransition.from}_${pTransition.to}_${pTransition.id}`;
|
|
216
|
+
if (pTransition.note) {
|
|
217
|
+
const lNoteName = `note_${lTransitionName}`;
|
|
218
|
+
const lNoteNodeName = `i_${lNoteName}`;
|
|
219
|
+
const lNoteNode = `\n${pIndent} "${lNoteNodeName}" [shape=point style=invis margin=0 width=0 height=0 fixedsize=true]`;
|
|
220
|
+
const lTransitionFrom = `\n${pIndent} "${pTransition.from}" -> "${lNoteNodeName}" [arrowhead=none${lTail}${lColorAttribute}]`;
|
|
221
|
+
const lTransitionTo = `\n${pIndent} "${lNoteNodeName}" -> "${pTransition.to}" [label="${lLabel}"${lHead}${lColorAttribute}${lFontColorAttribute}]`;
|
|
222
|
+
const lLineToNote = `\n${pIndent} "${lNoteNodeName}" -> "${lNoteName}" [style=dashed arrowtail=none arrowhead=none weight=0]`;
|
|
223
|
+
const lNote = `\n${pIndent} "${lNoteName}" [label="${noteToLabel(pTransition.note)}" shape=note fontsize=10 color=black fontcolor=black fillcolor="#ffffcc" penwidth=1.0]`;
|
|
224
|
+
return lNoteNode + lTransitionFrom + lTransitionTo + lLineToNote + lNote;
|
|
225
|
+
}
|
|
226
|
+
if (isCompositeSelf(pModel, pTransition)) {
|
|
227
|
+
const { lTailPorts, lHeadPorts } = getTransitionPorts(
|
|
228
|
+
pOptions,
|
|
229
|
+
pModel,
|
|
230
|
+
pTransition,
|
|
231
|
+
);
|
|
232
|
+
const lTransitionFrom = `\n${pIndent} "${pTransition.from}" -> "self_tr_${pTransition.from}_${pTransition.to}_${pTransition.id}" [label="${lLabel}" arrowhead=none class="${lClass}"${lTailPorts}${lTail}${lColorAttribute}${lFontColorAttribute}]`;
|
|
233
|
+
const lTransitionTo = `\n${pIndent} "self_tr_${pTransition.from}_${pTransition.to}_${pTransition.id}" -> "${pTransition.to}" [class="${lClass}"${lHead}${lHeadPorts}${lColorAttribute}${lPenWidth}]`;
|
|
234
|
+
return lTransitionFrom + lTransitionTo;
|
|
235
|
+
}
|
|
236
|
+
return `\n${pIndent} "${pTransition.from}" -> "${pTransition.to}" [label="${lLabel}" class="${lClass}"${lTail}${lHead}${lColorAttribute}${lFontColorAttribute}${lPenWidth}]`;
|
|
237
|
+
}
|
|
238
|
+
function transitions(pTransitions, pIndent, pOptions, pModel) {
|
|
239
|
+
return pTransitions
|
|
240
|
+
.map((pTransition) => transition(pTransition, pIndent, pOptions, pModel))
|
|
241
|
+
.join("");
|
|
242
|
+
}
|
|
243
|
+
export default function renderDot(pStateMachine, pOptions = {}, pIndent = "") {
|
|
244
|
+
const lGraphAttributes = buildGraphAttributes(
|
|
245
|
+
getOptionValue(pOptions, "engine"),
|
|
246
|
+
getOptionValue(pOptions, "direction"),
|
|
247
|
+
pOptions?.dotGraphAttrs || [],
|
|
248
|
+
);
|
|
249
|
+
const lNodeAttributes = buildNodeAttributes(pOptions.dotNodeAttrs || []);
|
|
250
|
+
const lEdgeAttributes = buildEdgeAttributes(pOptions.dotEdgeAttrs || []);
|
|
251
|
+
const lModel = new StateMachineModel(pStateMachine);
|
|
252
|
+
gRenderedTransitions = new Set();
|
|
253
|
+
const lStates = states(pStateMachine.states, pIndent, pOptions, lModel);
|
|
254
|
+
const lRemainingTransitions = transitions(
|
|
255
|
+
lModel.flattenedTransitions.filter(
|
|
256
|
+
(pTransition) => !gRenderedTransitions.has(pTransition.id),
|
|
257
|
+
),
|
|
258
|
+
pIndent,
|
|
259
|
+
pOptions,
|
|
260
|
+
lModel,
|
|
148
261
|
);
|
|
149
|
-
|
|
150
|
-
|
|
262
|
+
gRenderedTransitions = new Set();
|
|
263
|
+
return `digraph "state transitions" {
|
|
264
|
+
${lGraphAttributes}
|
|
265
|
+
node [${lNodeAttributes}]
|
|
266
|
+
edge [${lEdgeAttributes}]
|
|
267
|
+
|
|
268
|
+
${lStates}${lRemainingTransitions}
|
|
269
|
+
}
|
|
270
|
+
`;
|
|
271
|
+
}
|
package/dist/render/dot/utl.mjs
CHANGED
|
@@ -1,31 +1,103 @@
|
|
|
1
|
-
|
|
1
|
+
import he from "he";
|
|
2
|
+
import { getOptionValue } from "../../options.mjs";
|
|
3
|
+
function getStateColor(pState, pNodeAttributes) {
|
|
4
|
+
const lNodeColor = (pNodeAttributes || []).find(
|
|
5
|
+
(pAttribute) => pAttribute.name === "color",
|
|
6
|
+
)?.value;
|
|
7
|
+
if (
|
|
8
|
+
lNodeColor &&
|
|
9
|
+
!pState.color &&
|
|
10
|
+
[
|
|
11
|
+
"initial",
|
|
12
|
+
"fork",
|
|
13
|
+
"join",
|
|
14
|
+
"junction",
|
|
15
|
+
"forkjoin",
|
|
16
|
+
"terminate",
|
|
17
|
+
"final",
|
|
18
|
+
].includes(pState.type)
|
|
19
|
+
) {
|
|
20
|
+
return lNodeColor;
|
|
21
|
+
}
|
|
22
|
+
return pState.color ?? "black";
|
|
23
|
+
}
|
|
24
|
+
export function escapeString(pString) {
|
|
2
25
|
return pString
|
|
3
26
|
.replace(/\\/g, "\\\\")
|
|
4
27
|
.replace(/\n\s*/g, "\\l")
|
|
5
28
|
.replace(/"/g, '\\"')
|
|
6
29
|
.concat("\\l");
|
|
7
30
|
}
|
|
8
|
-
function escapeLabelString(pString) {
|
|
31
|
+
export function escapeLabelString(pString) {
|
|
9
32
|
return pString
|
|
10
33
|
.replace(/\\/g, "\\\\")
|
|
11
34
|
.replace(/\n\s*/g, " \\l")
|
|
12
35
|
.replace(/"/g, '\\"')
|
|
13
36
|
.concat(" \\l");
|
|
14
37
|
}
|
|
15
|
-
function isVertical(pDirection) {
|
|
38
|
+
export function isVertical(pDirection) {
|
|
16
39
|
const lDirection = pDirection || "top-down";
|
|
17
40
|
return lDirection === "top-down" || lDirection === "bottom-top";
|
|
18
41
|
}
|
|
19
|
-
function isCompositeSelf(pStateMachineModel, pTransition) {
|
|
42
|
+
export function isCompositeSelf(pStateMachineModel, pTransition) {
|
|
20
43
|
return (
|
|
21
44
|
pTransition.from === pTransition.to &&
|
|
22
|
-
pStateMachineModel.findStateByName(pTransition.from)
|
|
45
|
+
(pStateMachineModel.findStateByName(pTransition.from)?.statemachine ??
|
|
46
|
+
false) &&
|
|
23
47
|
pTransition.type !== "internal"
|
|
24
48
|
);
|
|
25
49
|
}
|
|
26
|
-
export
|
|
27
|
-
escapeString
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
50
|
+
export function noteToLabel(pNote) {
|
|
51
|
+
return pNote.map(escapeString).join("");
|
|
52
|
+
}
|
|
53
|
+
export function stateNote(pState, pIndent) {
|
|
54
|
+
if (pState.note) {
|
|
55
|
+
const lNoteName = `note_${pState.name}`;
|
|
56
|
+
let lReturnValue = `\n${pIndent} "${lNoteName}" [color=black fontcolor=black label="${noteToLabel(pState.note)}" shape=note fontsize=10 fillcolor="#ffffcc" penwidth=1.0]`;
|
|
57
|
+
lReturnValue += `\n${pIndent} "${pState.name}" -> "${lNoteName}" [style=dashed arrowtail=none arrowhead=none]`;
|
|
58
|
+
return lReturnValue;
|
|
59
|
+
}
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
export function normalizeState(pState, pOptions, pIndent) {
|
|
63
|
+
const lReturnValue = structuredClone(pState);
|
|
64
|
+
lReturnValue.colorAttribute = pState.color ? ` color="${pState.color}"` : "";
|
|
65
|
+
lReturnValue.fontColorAttribute = pState.color
|
|
66
|
+
? ` fontcolor="${pState.color}"`
|
|
67
|
+
: "";
|
|
68
|
+
lReturnValue.color = getStateColor(pState, pOptions.dotNodeAttrs);
|
|
69
|
+
lReturnValue.class = pState.class
|
|
70
|
+
? `state ${pState.type} ${pState.class}`
|
|
71
|
+
: `state ${pState.type}`;
|
|
72
|
+
lReturnValue.label = he.escape(pState.label ?? pState.name);
|
|
73
|
+
lReturnValue.noteText = stateNote(pState, pIndent);
|
|
74
|
+
if (
|
|
75
|
+
!pState.isParallelArea &&
|
|
76
|
+
pState.type === "parallel" &&
|
|
77
|
+
(pState.statemachine?.states ?? []).length > 0
|
|
78
|
+
) {
|
|
79
|
+
lReturnValue.statemachine.states = pState.statemachine.states.map(
|
|
80
|
+
(pChildState) => ({
|
|
81
|
+
...pChildState,
|
|
82
|
+
isParallelArea: pChildState.type === "regular",
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return lReturnValue;
|
|
87
|
+
}
|
|
88
|
+
export function formatActionType(pString) {
|
|
89
|
+
return pString === "activity" ? "" : `${pString}/ `;
|
|
90
|
+
}
|
|
91
|
+
export function getTransitionPorts(pOptions, pModel, pTransition) {
|
|
92
|
+
let lTailPorts = ' tailport="n" headport="n"';
|
|
93
|
+
let lHeadPorts = ' tailport="n"';
|
|
94
|
+
const lDirection = getOptionValue(pOptions, "direction");
|
|
95
|
+
if (isVertical(lDirection)) {
|
|
96
|
+
lTailPorts = ' tailport="e" headport="e"';
|
|
97
|
+
lHeadPorts = ' tailport="w"';
|
|
98
|
+
} else if (pModel.findStateByName(pTransition.from)?.parent ?? false) {
|
|
99
|
+
lTailPorts = ' tailport="n" headport="n"';
|
|
100
|
+
lHeadPorts = ' tailport="s"';
|
|
101
|
+
}
|
|
102
|
+
return { lTailPorts, lHeadPorts };
|
|
103
|
+
}
|