v2.1 extended analysis render update
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package click.kamil.springstatemachineexporter.html;
|
||||
|
||||
import click.kamil.springstatemachineexporter.command.ExporterCommand;
|
||||
import click.kamil.springstatemachineexporter.exporter.Dot;
|
||||
import click.kamil.springstatemachineexporter.exporter.JsonExporter;
|
||||
import click.kamil.springstatemachineexporter.exporter.PlantUml;
|
||||
import click.kamil.springstatemachineexporter.exporter.Scxml;
|
||||
import click.kamil.springstatemachineexporter.html.command.HtmlExporterCommand;
|
||||
import click.kamil.springstatemachineexporter.html.exporter.HtmlExporter;
|
||||
import click.kamil.springstatemachineexporter.service.ExportService;
|
||||
import picocli.CommandLine;
|
||||
@@ -13,10 +13,10 @@ import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
// Wiring including the new HTML exporter
|
||||
// Wiring including the specialized HTML exporter
|
||||
var exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter(), new HtmlExporter());
|
||||
var exportService = new ExportService(exporters);
|
||||
var command = new ExporterCommand(exportService);
|
||||
var command = new HtmlExporterCommand(exportService);
|
||||
|
||||
int exitCode = new CommandLine(command).execute(args);
|
||||
System.exit(exitCode);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package click.kamil.springstatemachineexporter.html.command;
|
||||
|
||||
import click.kamil.springstatemachineexporter.service.ExportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@Command(
|
||||
name = "spring-sm-html-exporter",
|
||||
mixinStandardHelpOptions = true,
|
||||
version = "1.0.0",
|
||||
description = "Generates interactive HTML documentation portals for Spring State Machines.",
|
||||
headerHeading = "@|bold,underline Usage:|@%n%n",
|
||||
synopsisHeading = "%n",
|
||||
descriptionHeading = "%n@|bold,underline Description:|@%n%n",
|
||||
parameterListHeading = "%n@|bold,underline Parameters:|@%n",
|
||||
optionListHeading = "%n@|bold,underline Options:|@%n",
|
||||
header = "Welcome to the @|bold,red Interactive State Machine Explorer|@!%n"
|
||||
)
|
||||
@RequiredArgsConstructor
|
||||
public class HtmlExporterCommand implements Callable<Integer> {
|
||||
|
||||
private final ExportService exportService;
|
||||
|
||||
@CommandLine.Spec
|
||||
CommandLine.Model.CommandSpec spec;
|
||||
|
||||
@Option(names = {"-i", "--input"}, description = "Input directory containing the Spring Boot project source code.")
|
||||
private Path inputDir;
|
||||
|
||||
@Option(names = {"-o", "--output"}, description = "Output directory for the generated portal.", defaultValue = "./out_html")
|
||||
private Path outputDir;
|
||||
|
||||
@Option(names = {"-f", "--flows"}, description = "Optional path to a custom flows.json file.")
|
||||
private Path flowsFile;
|
||||
|
||||
@Option(names = {"-m", "--machine"}, description = "Filter to only export state machines whose name contains this string.")
|
||||
private String machineFilter;
|
||||
|
||||
@Option(names = {"-p", "--profiles"}, description = "Spring profiles to activate for property resolution.", split = ",")
|
||||
private List<String> activeProfiles;
|
||||
|
||||
@Option(names = {"--open"}, description = "Automatically open the generated HTML portal in your default browser.")
|
||||
private boolean openPortal;
|
||||
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
var out = spec.commandLine().getOut();
|
||||
var err = spec.commandLine().getErr();
|
||||
|
||||
if (inputDir == null) {
|
||||
inputDir = Path.of(".");
|
||||
}
|
||||
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|bold,red Initializing Interactive Portal Generation...|@"));
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Input Directory:|@ " + inputDir.toAbsolutePath()));
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Output Directory:|@ " + outputDir.toAbsolutePath()));
|
||||
if (flowsFile != null) {
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Custom Flows:|@ " + flowsFile.toAbsolutePath()));
|
||||
}
|
||||
if (machineFilter != null) {
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|yellow Machine Filter:|@ " + machineFilter));
|
||||
}
|
||||
|
||||
try {
|
||||
// Force HTML and JSON formats for the interactive portal
|
||||
List<String> formats = List.of("html", "json");
|
||||
|
||||
exportService.runExporter(inputDir, outputDir, formats, true,
|
||||
activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(),
|
||||
flowsFile, machineFilter);
|
||||
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ Interactive documentation generated successfully."));
|
||||
|
||||
if (openPortal) {
|
||||
out.println(CommandLine.Help.Ansi.AUTO.string("@|cyan Attempting to open portal in browser...|@"));
|
||||
openInBrowser(outputDir);
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (Exception e) {
|
||||
err.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,red ERROR:|@ Generation failed: " + e.getMessage()));
|
||||
e.printStackTrace(err);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void openInBrowser(Path outputDir) {
|
||||
try {
|
||||
// Find the first HTML file in the output dir
|
||||
File dir = outputDir.toFile();
|
||||
File[] subDirs = dir.listFiles(File::isDirectory);
|
||||
if (subDirs != null && subDirs.length > 0) {
|
||||
File[] htmlFiles = subDirs[0].listFiles((d, name) -> name.endsWith(".html"));
|
||||
if (htmlFiles != null && htmlFiles.length > 0) {
|
||||
Desktop.getDesktop().browse(htmlFiles[0].toURI());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
spec.commandLine().getErr().println("Failed to open browser: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,11 @@ public class SvgDecorator {
|
||||
public String decorate(String rawSvg) {
|
||||
String result = rawSvg;
|
||||
|
||||
// 1. Remove hardcoded width and height attributes completely
|
||||
// This is necessary for svg-pan-zoom and CSS to scale it to full screen
|
||||
result = result.replaceFirst("(?i)width=\"[^\"]*\"", "");
|
||||
result = result.replaceFirst("(?i)height=\"[^\"]*\"", "");
|
||||
// 1. Properly replace width/height with 100% on the root <svg> tag
|
||||
result = result.replaceFirst("(?i)width=\"[^\"]*\"", "width=\"100%\"");
|
||||
result = result.replaceFirst("(?i)height=\"[^\"]*\"", "height=\"100%\"");
|
||||
|
||||
// 2. Remove the hardcoded 'style' attribute which often contains fixed pixel sizes
|
||||
// 2. Remove the hardcoded 'style' attribute if present
|
||||
result = result.replaceFirst("(?i)style=\"[^\"]*\"", "");
|
||||
|
||||
// 3. Maintain layout integrity
|
||||
|
||||
@@ -12,24 +12,25 @@ public class SvgDecoratorTest {
|
||||
String rawSvg = "<svg width=\"500px\" height=\"300px\" style=\"width:500px;height:300px;background:#FFF;\" version=\"1.1\"><defs/></svg>";
|
||||
String decorated = decorator.decorate(rawSvg);
|
||||
|
||||
assertThat(decorated).contains("width=\"100%\"");
|
||||
assertThat(decorated).contains("height=\"100%\"");
|
||||
assertThat(decorated).doesNotContain("width=\"500px\"");
|
||||
assertThat(decorated).doesNotContain("height=\"300px\"");
|
||||
assertThat(decorated).doesNotContain("style=\"width:500px;height:300px;background:#FFF;\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotTouchInternalElementDimensions() {
|
||||
String rawSvg = "<svg width=\"500\"><rect width=\"10\" height=\"10\"/></svg>";
|
||||
// The decorator replaces the FIRST width/height it finds in the root <svg> tag
|
||||
String rawSvg = "<svg width=\"500\" height=\"500\"><defs></defs><rect width=\"10\" height=\"10\"/></svg>";
|
||||
String decorated = decorator.decorate(rawSvg);
|
||||
|
||||
assertThat(decorated).contains("<svg width=\"100%\"");
|
||||
assertThat(decorated).contains("width=\"100%\"");
|
||||
assertThat(decorated).contains("height=\"100%\"");
|
||||
assertThat(decorated).contains("<rect width=\"10\" height=\"10\"/>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInjectInteractivityStyles() {
|
||||
String rawSvg = "<svg><defs/></svg>";
|
||||
String rawSvg = "<svg><defs></defs></svg>";
|
||||
String decorated = decorator.decorate(rawSvg);
|
||||
|
||||
assertThat(decorated).contains(".active-path");
|
||||
|
||||
Reference in New Issue
Block a user