From 571920cf321700602319ddd64d84e0414a3d6a2d Mon Sep 17 00:00:00 2001 From: Mikhail Fedotov Date: Fri, 16 Feb 2024 23:37:59 +0700 Subject: [PATCH] Add metaInfo notes --- .../kotlin/ru/nsk/kstatemachine/MetaInfo.kt | 26 +++++++--- .../visitors/ExportPlantUmlVisitor.kt | 21 +++++++- .../PlantUmlExportWithMetaInfoSample.kt | 52 +++++-------------- .../nsk/kstatemachine/ExportToPlantUmlTest.kt | 21 ++++++-- 4 files changed, 69 insertions(+), 51 deletions(-) diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/MetaInfo.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/MetaInfo.kt index 6cf32e2..f757728 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/MetaInfo.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/MetaInfo.kt @@ -1,21 +1,31 @@ package ru.nsk.kstatemachine /** - * Additional static (designed to be immutable) info for library primitives. - * Users may extend this interface to add their own [MetaInfo] implementations + * Additional static (designed to be immutable) info for library primitives like [IState] [Transition] etc. + * Users may extend this interface to add their own [MetaInfo] implementations. */ interface MetaInfo /** - * Standard meta info, to control export PlantUML and Mermaid feature visualization. + * Standard [MetaInfo], to control export PlantUML and Mermaid feature visualization. */ interface IUmlMetaInfo : MetaInfo { /** - * Will be mapped to "long name" for state, and a "label" for transition + * Will be mapped to "long name" for [IState], and a "label" for [Transition] */ val umlLabel: String? - val stateDescriptions: List - val notes: List + /** + * Add description lines for [IState] + * Does not have effect for [Transition] + */ + val umlStateDescriptions: List + + /** + * For [IState] translated to "note right of". + * For [Transition] translated to "note on link" (supports only one note). + * Mermaid does not support this, so it will not take any effect. + */ + val umlNotes: List } /** @@ -24,6 +34,6 @@ interface IUmlMetaInfo : MetaInfo { */ data class UmlMetaInfo( override val umlLabel: String? = null, - override val stateDescriptions: List = emptyList(), - override val notes: List = emptyList(), + override val umlStateDescriptions: List = emptyList(), + override val umlNotes: List = emptyList(), ): IUmlMetaInfo \ No newline at end of file diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/visitors/ExportPlantUmlVisitor.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/visitors/ExportPlantUmlVisitor.kt index 9e2f64a..394c76e 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/visitors/ExportPlantUmlVisitor.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/visitors/ExportPlantUmlVisitor.kt @@ -64,6 +64,7 @@ internal class ExportPlantUmlVisitor( @Suppress("UNCHECKED_CAST") val targetStates = state.resolveTargetState(makeDirectionProducerPolicy()) .targetStates as Set + state.printStateNotes() targetStates.forEach { targetState -> crossLevelTransitions += "${state.graphName()} --> ${targetState.targetGraphName()}" } @@ -71,6 +72,7 @@ internal class ExportPlantUmlVisitor( else -> { line("state ${state.labelGraphName()}") state.printStateDescriptions() + state.printStateNotes() } } } else { @@ -107,6 +109,14 @@ internal class ExportPlantUmlVisitor( else crossLevelTransitions += transitionString } + + if (format != MERMAID) { // Mermaid does not support this + transition.metaInfo?.umlNotes?.forEach { + line("note on link") + line("$SINGLE_INDENT$it") + line("end note") + } + } } private fun makeDirectionProducerPolicy(): TransitionDirectionProducerPolicy { @@ -120,6 +130,8 @@ internal class ExportPlantUmlVisitor( private suspend fun processStateBody(state: IState) { state.printStateDescriptions() + state.printStateNotes() + val states = state.states.toList() // visit child states for (s in states.indices) { @@ -153,11 +165,16 @@ internal class ExportPlantUmlVisitor( } private fun IState.printStateDescriptions() { - val stateDescriptions = (metaInfo as? IUmlMetaInfo)?.stateDescriptions.orEmpty() - stateDescriptions.forEach { line("${graphName()} : $it") } + val descriptions = (metaInfo as? IUmlMetaInfo)?.umlStateDescriptions.orEmpty() + descriptions.forEach { line("${graphName()} : $it") } + } + + private fun IState.printStateNotes() { + metaInfo?.umlNotes?.forEach { line("note right of ${graphName()} : $it") } } private companion object { + val MetaInfo.umlNotes get() = (this as? IUmlMetaInfo)?.umlNotes.orEmpty() val MetaInfo.umlLabel get() = (this as? IUmlMetaInfo)?.umlLabel fun IState.graphName(): String { diff --git a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt index a3c6128..9a9cf98 100644 --- a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt +++ b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt @@ -13,50 +13,26 @@ private object PlantUmlExportWithMetaInfoSample { * The sample shows hot to use [MetaInfo] to beautify export output */ fun main() = runBlocking { + lateinit var state2: State val machine = createStateMachine(this) { - // label for state machine - metaInfo = UmlMetaInfo("Nested states sm") + metaInfo = UmlMetaInfo(umlLabel = "Nested states sm") - val state1 = initialState("State1") { - // label for state - metaInfo = UmlMetaInfo("State 1") - } - val state3 = finalState("State3") { - // label for state - metaInfo = UmlMetaInfo("State 3") - } - - val state2 = state("State2") { - // label for state - metaInfo = UmlMetaInfo("State 2") - transition { - // label for transition - metaInfo = UmlMetaInfo("That's all") - targetState = state3 - } - transition { - // label for transition - metaInfo = UmlMetaInfo("back to State 1") - targetState = state1 - } - val finalSubState = finalState { - // label for state - metaInfo = UmlMetaInfo("Final sub state") - } - initialState("Initial subState") { - transition { targetState = finalSubState } + initialState("State1") { + metaInfo = UmlMetaInfo("State 1 Label") + transitionOn { + metaInfo = UmlMetaInfo("Transition to State 2") + targetState = { state2 } } } - state1 { - transition { - metaInfo = UmlMetaInfo("go to ${state2.name}") - targetState = state2 - } - transition { targetState = this@state1 } - transition() + state2 = finalState("State2") { + metaInfo = UmlMetaInfo( + umlLabel = "FinalState 2 Label", + umlStateDescriptions = listOf("Description 1", "Description 2"), + umlNotes = listOf("Note 1", "Note 2"), + ) } } - println(machine.exportToPlantUml()) + println(machine.exportToPlantUml(showEventLabels = true)) } \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt index 61e8e6e..84c5aca 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/ExportToPlantUmlTest.kt @@ -191,12 +191,16 @@ hide empty description state State_1 { state State12 state "Choice label" as ChoiceState <> + note right of ChoiceState : Note 1 + note right of ChoiceState : Note 2 [*] --> ChoiceState } state "Long State 3" as State3 State3 : Description 1 State3 : Description 2 +note right of State3 : Note 1 +note right of State3 : Note 2 state "Long State 2" as State2 { state "Final sub state" as FinalState state Initial_subState @@ -209,6 +213,12 @@ state "Long State 2" as State2 { [*] --> State_1 State_1 --> State2 : go to State2, SwitchEvent State_1 --> State_1 : self targeted, SwitchEvent +note on link + Note 1 +end note +note on link + Note 2 +end note State2 --> State3 : That's all, SwitchEvent State2 --> State_1 : back to State 1, SwitchEvent State3 --> [*] @@ -375,7 +385,8 @@ class ExportToPlantUmlTest : StringSpec({ // label for state metaInfo = UmlMetaInfo( umlLabel = "Long State 3", - stateDescriptions = listOf("Description 1", "Description 2") + umlStateDescriptions = listOf("Description 1", "Description 2"), + umlNotes = listOf("Note 1", "Note 2"), ) } @@ -406,7 +417,10 @@ class ExportToPlantUmlTest : StringSpec({ metaInfo = UmlMetaInfo("go to ${state2.name}") targetState = state2 } - transition("self targeted") { targetState = this@state1 } + transition("self targeted") { + targetState = this@state1 + metaInfo = UmlMetaInfo(umlNotes = listOf("Note 1", "Note 2")) + } transition() val state12 = state("State12") @@ -414,7 +428,8 @@ class ExportToPlantUmlTest : StringSpec({ choiceState.metaInfo = UmlMetaInfo( umlLabel = "Choice label", // no plantUml nor Mermaid can draw this - stateDescriptions = listOf("Description 1", "Description 2") + umlStateDescriptions = listOf("Description 1", "Description 2"), + umlNotes = listOf("Note 1", "Note 2") ) } }