Skip to content

Commit

Permalink
Add metaInfo stateDescriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
nsk90 committed Feb 16, 2024
1 parent 908a280 commit 05dcac3
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface IUmlMetaInfo : MetaInfo {
*/
val umlLabel: String?
val stateDescriptions: List<String>
val notes: List<String>
}

/**
Expand All @@ -23,5 +24,6 @@ interface IUmlMetaInfo : MetaInfo {
*/
data class UmlMetaInfo(
override val umlLabel: String? = null,
override val stateDescriptions: List<String> = emptyList()
override val stateDescriptions: List<String> = emptyList(),
override val notes: List<String> = emptyList(),
): IUmlMetaInfo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import ru.nsk.kstatemachine.TransitionDirectionProducerPolicy.UnsafeCollectTarge
import ru.nsk.kstatemachine.visitors.CompatibilityFormat.MERMAID
import ru.nsk.kstatemachine.visitors.CompatibilityFormat.PLANT_UML

private const val STAR = "[*]"
private const val SINGLE_INDENT = " "
private const val PARALLEL = "--"
private const val SHALLOW_HISTORY = "[H]"
private const val DEEP_HISTORY = "[H*]"
private const val CHOICE = "<<choice>>"

/**
* This object will be unsafely cast to any kind of [Event],
* causing runtime failures if user defined (conditional) lambdas will touch this object.
Expand Down Expand Up @@ -61,7 +68,10 @@ internal class ExportPlantUmlVisitor(
crossLevelTransitions += "${state.graphName()} --> ${targetState.targetGraphName()}"
}
}
else -> line("state ${state.labelGraphName()}")
else -> {
line("state ${state.labelGraphName()}")
state.printStateDescriptions()
}
}
} else {
if (state !is StateMachine) { // ignore composed machines
Expand Down Expand Up @@ -109,6 +119,7 @@ internal class ExportPlantUmlVisitor(
}

private suspend fun processStateBody(state: IState) {
state.printStateDescriptions()
val states = state.states.toList()
// visit child states
for (s in states.indices) {
Expand Down Expand Up @@ -141,13 +152,13 @@ internal class ExportPlantUmlVisitor(
return " : $text".takeIf { text.isNotBlank() } ?: ""
}

private fun IState.printStateDescriptions() {
val stateDescriptions = (metaInfo as? IUmlMetaInfo)?.stateDescriptions.orEmpty()
stateDescriptions.forEach { line("${graphName()} : $it") }
}

private companion object {
const val STAR = "[*]"
const val SINGLE_INDENT = " "
const val PARALLEL = "--"
const val SHALLOW_HISTORY = "[H]"
const val DEEP_HISTORY = "[H*]"
const val CHOICE = "<<choice>>"
val MetaInfo.umlLabel get() = (this as? IUmlMetaInfo)?.umlLabel

fun IState.graphName(): String {
val name = (name ?: "State${hashCode()}").replace(Regex("[ -]"), "_")
Expand All @@ -173,12 +184,11 @@ internal class ExportPlantUmlVisitor(
}
}

private val MetaInfo.umlLabel: String? get() = (this as? IUmlMetaInfo)?.umlLabel

/**
* Export [StateMachine] to PlantUML state diagram
* @see <a href="https://plantuml.com/">PlantUML</a>
*
* [showEventLabels] prints event types for transitions
* [unsafeCallConditionalLambdas] will call conditional lambdas which can touch application data,
* this may give more complete output, but may be not safe.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ru.nsk.kstatemachine.visitors.CompatibilityFormat.MERMAID
* Export [StateMachine] to Mermaid state diagram
* @see <a href="https://mermaid.js.org/">Mermaid</a>
*
* [showEventLabels] prints event types for transitions
* [unsafeCallConditionalLambdas] will call conditional lambdas which can touch application data,
* this may give more complete output, but may be not safe.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ private const val PLANTUML_PARALLEL_STATES_RESULT = """@startuml
hide empty description
state parallel_states {
state "State 1" as State1 {
state State11
state State_11
state State12
[*] --> State11
State11 --> State12 : to State 12
State12 --> State11
[*] --> State_11
State_11 --> State12 : to State 12
State12 --> State_11
}
--
state State2 {
Expand Down Expand Up @@ -188,9 +188,16 @@ state1 --> state222

private const val PLANTUML_META_INFO = """@startuml
hide empty description
state State1
state "State 3" as State3
state "State 2" as State2 {
state State_1 {
state State12
state "Choice label" as ChoiceState <<choice>>
[*] --> ChoiceState
}
state "Long State 3" as State3
State3 : Description 1
State3 : Description 2
state "Long State 2" as State2 {
state "Final sub state" as FinalState
state Initial_subState
Expand All @@ -199,12 +206,13 @@ state "State 2" as State2 {
FinalState --> [*]
}
[*] --> State1
State1 --> State2 : go to State2, SwitchEvent
State1 --> State1 : self targeted, SwitchEvent
[*] --> State_1
State_1 --> State2 : go to State2, SwitchEvent
State_1 --> State_1 : self targeted, SwitchEvent
State2 --> State3 : That's all, SwitchEvent
State2 --> State1 : back to State 1, SwitchEvent
State2 --> State_1 : back to State 1, SwitchEvent
State3 --> [*]
ChoiceState --> State12
@enduml
"""

Expand Down Expand Up @@ -276,7 +284,7 @@ class ExportToPlantUmlTest : StringSpec({
initialState("parallel states", ChildMode.PARALLEL) {
state("State1") {
metaInfo = UmlMetaInfo("State 1")
val state11 = initialState("State11")
val state11 = initialState("State-11")
val state12 = state("State12")

state11 {
Expand Down Expand Up @@ -362,15 +370,18 @@ class ExportToPlantUmlTest : StringSpec({
// label for state machine
metaInfo = UmlMetaInfo("Nested states sm")

val state1 = initialState("State1")
val state1 = initialState("State-1")
val state3 = finalState("State3") {
// label for state
metaInfo = UmlMetaInfo("State 3")
metaInfo = UmlMetaInfo(
umlLabel = "Long State 3",
stateDescriptions = listOf("Description 1", "Description 2")
)
}

val state2 = state("State2") {
// label for state
metaInfo = UmlMetaInfo("State 2")
metaInfo = UmlMetaInfo("Long State 2")
transition<SwitchEvent> {
// label for transition
metaInfo = UmlMetaInfo("That's all")
Expand All @@ -397,10 +408,21 @@ class ExportToPlantUmlTest : StringSpec({
}
transition<SwitchEvent>("self targeted") { targetState = this@state1 }
transition<SwitchEvent>()

val state12 = state("State12")
val choiceState = initialChoiceState("ChoiceState") { state12 }
choiceState.metaInfo = UmlMetaInfo(
umlLabel = "Choice label",
// no plantUml nor Mermaid can draw this
stateDescriptions = listOf("Description 1", "Description 2")
)
}
}

machine.exportToPlantUml(showEventLabels = true) shouldBe PLANTUML_META_INFO
machine.exportToPlantUml(
showEventLabels = true,
unsafeCallConditionalLambdas = true
) shouldBe PLANTUML_META_INFO
}
}
})

0 comments on commit 05dcac3

Please sign in to comment.