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 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="659" alt="colored states and transitions" src="https://raw.githubusercontent.com/sverweij/state-machine-cat/main/docs/pics/10colored_states_and_transitions.png">
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 satisfies from "semver/functions/satisfies.js";
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(`${$package.version}\n`, "utf8");
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 lStateMachine = parse.getAST(pScript, pOptions);
8
- const lDesugar = options.getOptionValue(pOptions, "desugar");
9
- return getRenderFunction(options.getOptionValue(pOptions, "outputType"))(
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
- pOptions,
12
+ lOptions,
12
13
  );
13
14
  }
14
15
  export const version = _version;
package/dist/options.mjs CHANGED
@@ -8,7 +8,6 @@ const ALLOWED_VALUES = Object.freeze({
8
8
  values: [
9
9
  { name: "ast" },
10
10
  { name: "dot" },
11
- { name: "ndot" },
12
11
  { name: "eps" },
13
12
  { name: "json" },
14
13
  { name: "oldeps" },
@@ -1,6 +1,6 @@
1
1
  import fastxml from "fast-xml-parser";
2
2
  import he from "he";
3
- import traverse from "traverse";
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: (pEngine, pDirection, pDotGraphAttributes) =>
41
- GENERIC_GRAPH_ATTRIBUTES.concat(GRAPH_ATTRIBUTES[pEngine] || [])
42
- .concat(DIRECTION_ATTRIBUTES[pDirection] || [])
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 options from "../../options.mjs";
1
+ import he from "he";
2
+ import { getOptionValue } from "../../options.mjs";
2
3
  import StateMachineModel from "../../state-machine-model.mjs";
3
- import attributebuilder from "./attributebuilder.mjs";
4
- import stateTransformers from "./state-transformers.mjs";
5
- import transitionTransformers from "./transition-transformers.mjs";
6
- import renderDotFromAST from "./render-dot-from-ast.mjs";
7
- import utl from "./utl.mjs";
8
- function addExternalSelfTransitions(pStateMachineModel) {
9
- return (pState) => {
10
- if (Object.hasOwn(pState, "statemachine")) {
11
- pState.nestedExternalSelfTransitions = pStateMachineModel
12
- .findExternalSelfTransitions(pState.name)
13
- .map((pTransition) => pTransition.name);
14
- }
15
- return pState;
16
- };
17
- }
18
- function transformStates(
19
- pStates,
20
- pDirection,
21
- pNodeAttributes,
22
- pStateMachineModel,
23
- ) {
24
- pStates
25
- .filter((pState) => pState.statemachine)
26
- .forEach((pState) => {
27
- pState.statemachine.states = transformStates(
28
- pState.statemachine.states,
29
- pDirection,
30
- pNodeAttributes,
31
- pStateMachineModel,
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
- return pStates
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
- return { ...pTransition, ...lAdditionalAttributes };
102
- };
103
- }
104
- function nameTransition() {
105
- return (pTransition) => {
106
- pTransition.name = `tr_${pTransition.from}_${pTransition.to}_${pTransition.id}`;
107
- if (pTransition.note) {
108
- pTransition.noteName = `note_${pTransition.name}`;
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
- lStateMachine.states = transformStates(
132
- lStateMachine.states,
133
- pOptions.direction,
134
- pOptions.dotNodeAttrs,
135
- lStateMachineModel,
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
- lStateMachine = splitStates(lStateMachine);
138
- lStateMachine.graphAttributes = attributebuilder.buildGraphAttributes(
139
- options.getOptionValue(pOptions, "engine"),
140
- options.getOptionValue(pOptions, "direction"),
141
- pOptions.dotGraphAttrs,
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
- lStateMachine.nodeAttributes = attributebuilder.buildNodeAttributes(
144
- pOptions.dotNodeAttrs,
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
- lStateMachine.edgeAttributes = attributebuilder.buildEdgeAttributes(
147
- pOptions.dotEdgeAttrs,
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
- return renderDotFromAST(lStateMachine);
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
+ }
@@ -1,31 +1,103 @@
1
- function escapeString(pString) {
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).statemachine &&
45
+ (pStateMachineModel.findStateByName(pTransition.from)?.statemachine ??
46
+ false) &&
23
47
  pTransition.type !== "internal"
24
48
  );
25
49
  }
26
- export default {
27
- escapeString,
28
- escapeLabelString,
29
- isVertical,
30
- isCompositeSelf,
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
+ }