styling
This commit is contained in:
@@ -42,6 +42,9 @@ public class ExporterCommand implements Callable<Integer> {
|
||||
@Option(names = {"-f", "--format"}, description = "Comma-separated list of formats to export (dot, puml, scxml, json). If omitted, all formats are exported.", split = ",")
|
||||
private List<String> selectedFormats;
|
||||
|
||||
@Option(names = {"--no-diamonds"}, description = "Render choice/junction states as regular states instead of diamonds.", negatable = true, defaultValue = "true")
|
||||
private boolean renderChoicesAsDiamonds;
|
||||
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
var out = spec.commandLine().getOut();
|
||||
@@ -66,7 +69,7 @@ public class ExporterCommand implements Callable<Integer> {
|
||||
if (jsonFile != null) {
|
||||
exportService.runJsonExporter(jsonFile, outputDir, selectedFormats);
|
||||
} else {
|
||||
exportService.runExporter(inputDir, outputDir, selectedFormats);
|
||||
exportService.runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ All state machines have been exported to " + outputDir.toAbsolutePath()));
|
||||
return 0;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class Dot implements StateMachineExporter {
|
||||
List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
ExportOptions options) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("digraph statemachine {\n");
|
||||
sb.append(" rankdir=LR;\n");
|
||||
@@ -47,7 +47,7 @@ public class Dot implements StateMachineExporter {
|
||||
}
|
||||
}
|
||||
|
||||
boolean includeChoiceStates = !statesWithChoice.isEmpty();
|
||||
boolean includeChoiceStates = options.isRenderChoicesAsDiamonds() && !statesWithChoice.isEmpty();
|
||||
if (includeChoiceStates) {
|
||||
for (String choice : statesWithChoice) {
|
||||
String color = choiceColorMap.get(choice);
|
||||
@@ -104,7 +104,7 @@ public class Dot implements StateMachineExporter {
|
||||
if (t.getGuard() != null && !t.getGuard().expression().isBlank()) {
|
||||
if (!label.isEmpty())
|
||||
label.append(" ");
|
||||
String guardText = useLambdaGuards && t.getGuard().isLambda() ? "λ"
|
||||
String guardText = options.isUseLambdaGuards() && t.getGuard().isLambda() ? "λ"
|
||||
: escapeDotString(t.getGuard().expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
|
||||
label.append("[").append(guardText).append("]");
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class Dot implements StateMachineExporter {
|
||||
if (i > 0)
|
||||
actionBuilder.append(", ");
|
||||
var action = t.getActions().get(i);
|
||||
actionBuilder.append(useLambdaGuards && action.isLambda() ? "λ" : escapeDotString(action.expression()));
|
||||
actionBuilder.append(options.isUseLambdaGuards() && action.isLambda() ? "λ" : escapeDotString(action.expression()));
|
||||
}
|
||||
if (!label.isEmpty())
|
||||
label.append(" / ");
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package click.kamil.springstatemachineexporter.exporter;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class ExportOptions {
|
||||
@Builder.Default
|
||||
boolean useLambdaGuards = true;
|
||||
@Builder.Default
|
||||
boolean renderChoicesAsDiamonds = true;
|
||||
}
|
||||
@@ -18,13 +18,14 @@ public class JsonExporter implements StateMachineExporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String export(String name, List<Transition> transitions, Set<String> startStates, Set<String> endStates, boolean useLambdaGuards) {
|
||||
public String export(String name, List<Transition> transitions, Set<String> startStates, Set<String> endStates, ExportOptions options) {
|
||||
try {
|
||||
StateMachineModel model = StateMachineModel.builder()
|
||||
.name(name)
|
||||
.transitions(transitions)
|
||||
.startStates(startStates)
|
||||
.endStates(endStates)
|
||||
.renderChoicesAsDiamonds(options.isRenderChoicesAsDiamonds())
|
||||
.build();
|
||||
return objectMapper.writeValueAsString(model);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PlantUml implements StateMachineExporter {
|
||||
List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
ExportOptions options) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("@startuml\n");
|
||||
@@ -77,7 +77,9 @@ public class PlantUml implements StateMachineExporter {
|
||||
|
||||
for (String state : statesWithChoice) {
|
||||
String className = "choice_color_" + (colorIndex % choiceColors.size());
|
||||
sb.append("state ").append(state).append(" <<choice>>\n");
|
||||
if (options.isRenderChoicesAsDiamonds()) {
|
||||
sb.append("state ").append(state).append(" <<choice>>\n");
|
||||
}
|
||||
choiceStateClassMap.put(state, className);
|
||||
colorIndex++;
|
||||
}
|
||||
@@ -88,7 +90,9 @@ public class PlantUml implements StateMachineExporter {
|
||||
.map(s -> simplify(s.toString()))
|
||||
.collect(Collectors.toSet());
|
||||
for (String junction : junctionStates) {
|
||||
sb.append("state ").append(junction).append(" <<choice>>\n");
|
||||
if (options.isRenderChoicesAsDiamonds()) {
|
||||
sb.append("state ").append(junction).append(" <<choice>>\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
@@ -128,7 +132,7 @@ public class PlantUml implements StateMachineExporter {
|
||||
usedEventStereotypes.add(eventClass);
|
||||
}
|
||||
|
||||
String label = buildLabel(t, useLambdaGuards);
|
||||
String label = buildLabel(t, options.isUseLambdaGuards());
|
||||
if (!label.isEmpty()) {
|
||||
sb.append(" : ").append(label);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Scxml implements StateMachineExporter {
|
||||
List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
ExportOptions options) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
sb.append("<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"");
|
||||
@@ -53,7 +53,7 @@ public class Scxml implements StateMachineExporter {
|
||||
}
|
||||
|
||||
for (State target : targets) {
|
||||
sb.append(renderTransition(t, target, useLambdaGuards));
|
||||
sb.append(renderTransition(t, target, options.isUseLambdaGuards()));
|
||||
}
|
||||
}
|
||||
sb.append(" </state>\n");
|
||||
|
||||
@@ -10,7 +10,7 @@ public interface StateMachineExporter {
|
||||
List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards);
|
||||
ExportOptions options);
|
||||
|
||||
// Helper: extract last enum part
|
||||
default String simplify(String full) {
|
||||
|
||||
@@ -17,4 +17,6 @@ public class StateMachineModel {
|
||||
private List<Transition> transitions;
|
||||
private Set<String> startStates;
|
||||
private Set<String> endStates;
|
||||
@Builder.Default
|
||||
private boolean renderChoicesAsDiamonds = true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils;
|
||||
import click.kamil.springstatemachineexporter.model.StateMachineModel;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import click.kamil.springstatemachineexporter.exporter.ExportOptions;
|
||||
import click.kamil.springstatemachineexporter.exporter.StateMachineExporter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -28,19 +29,19 @@ public class ExportService {
|
||||
private static final List<String> TARGET_ANNOTATIONS = List.of("EnableStateMachineFactory", "EnableStateMachine");
|
||||
public static final Set<String> STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory");
|
||||
|
||||
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats) throws IOException {
|
||||
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(inputDir);
|
||||
exportAll(context, outputDir, selectedFormats);
|
||||
exportAll(context, outputDir, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
|
||||
public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats) throws IOException {
|
||||
JsonImportService jsonImportService = new JsonImportService();
|
||||
StateMachineModel model = jsonImportService.importJson(jsonFile);
|
||||
generateOutputs(outputDir, model.getName(), model.getTransitions(), model.getStartStates(), model.getEndStates(), selectedFormats);
|
||||
generateOutputs(outputDir, model.getName(), model.getTransitions(), model.getStartStates(), model.getEndStates(), selectedFormats, model.isRenderChoicesAsDiamonds());
|
||||
}
|
||||
|
||||
private void exportAll(CodebaseContext context, Path outputDir, List<String> selectedFormats) throws IOException {
|
||||
private void exportAll(CodebaseContext context, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
|
||||
Set<String> processedLocations = new HashSet<>();
|
||||
|
||||
// 1. Find entry point classes (annotated or extending adapter)
|
||||
@@ -48,7 +49,7 @@ public class ExportService {
|
||||
for (TypeDeclaration td : entryPoints) {
|
||||
String fqn = context.getFqn(td);
|
||||
if (processedLocations.add(fqn)) {
|
||||
processEntryPoint(td, outputDir, context, selectedFormats);
|
||||
processEntryPoint(td, outputDir, context, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +62,12 @@ public class ExportService {
|
||||
String uniqueId = parentFqn + "#" + methodName;
|
||||
|
||||
if (processedLocations.add(uniqueId)) {
|
||||
processBeanMethod(m, outputDir, context, selectedFormats);
|
||||
processBeanMethod(m, outputDir, context, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context, List<String> selectedFormats) throws IOException {
|
||||
private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
|
||||
String className = context.getFqn(td);
|
||||
log.info("Processing state machine config: {}", className);
|
||||
|
||||
@@ -83,10 +84,10 @@ public class ExportService {
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions, initialStatesAst);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst);
|
||||
|
||||
generateOutputs(outputDir, className, transitions, startStates, endStates, selectedFormats);
|
||||
generateOutputs(outputDir, className, transitions, startStates, endStates, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
|
||||
private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context, List<String> selectedFormats) throws IOException {
|
||||
private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
|
||||
TypeDeclaration parentClass = (TypeDeclaration) m.getParent();
|
||||
String parentFqn = context.getFqn(parentClass);
|
||||
String methodName = m.getName().getIdentifier();
|
||||
@@ -99,22 +100,27 @@ public class ExportService {
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
generateOutputs(outputDir, uniqueName, transitions, startStates, endStates, selectedFormats);
|
||||
generateOutputs(outputDir, uniqueName, transitions, startStates, endStates, selectedFormats, renderChoicesAsDiamonds);
|
||||
}
|
||||
|
||||
private void generateOutputs(Path outputDir, String baseName, List<Transition> transitions,
|
||||
Set<String> startStates, Set<String> endStates, List<String> selectedFormats) throws IOException {
|
||||
Set<String> startStates, Set<String> endStates, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
|
||||
|
||||
Path targetDir = outputDir.resolve(baseName);
|
||||
Files.createDirectories(targetDir);
|
||||
|
||||
ExportOptions options = ExportOptions.builder()
|
||||
.useLambdaGuards(true)
|
||||
.renderChoicesAsDiamonds(renderChoicesAsDiamonds)
|
||||
.build();
|
||||
|
||||
for (StateMachineExporter output : exporters) {
|
||||
if (selectedFormats != null && !selectedFormats.isEmpty()) {
|
||||
boolean match = selectedFormats.stream().anyMatch(f -> output.getFileExtension().toLowerCase().contains(f.toLowerCase()));
|
||||
if (!match) continue;
|
||||
}
|
||||
|
||||
String content = output.export(baseName, transitions, startStates, endStates, true);
|
||||
String content = output.export(baseName, transitions, startStates, endStates, options);
|
||||
String fileName = baseName + output.getFileExtension();
|
||||
|
||||
try (PrintWriter out = new PrintWriter(targetDir.resolve(fileName).toFile())) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<style>
|
||||
state {
|
||||
.choice {
|
||||
BackgroundColor LightYellow
|
||||
}
|
||||
}
|
||||
arrow {
|
||||
LineThickness 1
|
||||
.external { LineColor #1E90FF }
|
||||
.internal { LineColor #32CD32 }
|
||||
.local { LineColor #FFA500 }
|
||||
.junction { LineColor #FF69B4 }
|
||||
.join { LineColor #8A2BE2 }
|
||||
.fork { LineColor #20B2AA }
|
||||
.choice_type { LineColor #000000 }
|
||||
/* CHOICE_COLORS_PLACEHOLDER */
|
||||
}
|
||||
/* Example: Highlight all transitions for a specific event */
|
||||
/* .e_MY_EVENT { LineColor red } */
|
||||
</style>
|
||||
|
||||
hide <<external>> stereotype
|
||||
hide <<internal>> stereotype
|
||||
hide <<local>> stereotype
|
||||
hide <<junction>> stereotype
|
||||
hide <<join>> stereotype
|
||||
hide <<fork>> stereotype
|
||||
hide <<choice_type>> stereotype
|
||||
/* HIDE_CHOICE_COLORS_PLACEHOLDER */
|
||||
Reference in New Issue
Block a user