Skip to content

Commit 028c83a

Browse files
authored
Improving the behaviour (#16)
* Improving the behaviour * Adding Dependabot
1 parent f1825fc commit 028c83a

13 files changed

Lines changed: 208 additions & 42 deletions

File tree

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "maven"
4+
directory: "/"
5+
schedule:
6+
interval: "daily"
7+
time: "05:00"
8+
- package-ecosystem: "github-actions"
9+
directory: "/"
10+
schedule:
11+
interval: "daily"
12+
time: "05:00"

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.0] 2025/06/05
9+
10+
### Changed
11+
12+
- Improved the behavior to update the png file if the app detect recent changes in the puml .file although exist a png file. This behaviour is perfect if the user are updating the .puml file in an interactive way.
13+
814
## [0.3.0] 2025/06/03
915

1016
### Changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ jwebserver -p 8001 -d "$(pwd)/target/site/jacoco"
3232

3333
```bash
3434
./mvnw clean package
35-
java -jar target/puml-to-png-0.3.0.jar --help
36-
java -jar target/puml-to-png-0.3.0.jar --file ./docs/sample-diagram.puml
37-
java -jar target/puml-to-png-0.3.0.jar --watch docs
35+
java -jar target/puml-to-png-0.4.0.jar --help
36+
java -jar target/puml-to-png-0.4.0.jar --file ./docs/sample-diagram.puml
37+
java -jar target/puml-to-png-0.4.0.jar --watch docs
3838

3939
jbang cache clear
4040
jbang catalog list

docs/sample-diagram.png

-1 Bytes
Loading

pom.xml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>info.jab.cli</groupId>
77
<artifactId>puml-to-png</artifactId>
8-
<version>0.3.0</version>
8+
<version>0.4.0</version>
99

1010
<properties>
1111
<java.version>24</java.version>
@@ -15,6 +15,8 @@
1515

1616
<!-- Dependencies -->
1717
<jspecify.version>1.0.0</jspecify.version>
18+
<slf4j.version>2.0.17</slf4j.version>
19+
<logback.version>1.5.18</logback.version>
1820
<picocli.version>4.7.7</picocli.version>
1921
<jcolor.version>5.5.1</jcolor.version>
2022
<jfiglet.version>0.0.9</jfiglet.version>
@@ -73,6 +75,16 @@
7375
<artifactId>jspecify</artifactId>
7476
<version>${jspecify.version}</version>
7577
</dependency>
78+
<dependency>
79+
<groupId>org.slf4j</groupId>
80+
<artifactId>slf4j-api</artifactId>
81+
<version>${slf4j.version}</version>
82+
</dependency>
83+
<dependency>
84+
<groupId>ch.qos.logback</groupId>
85+
<artifactId>logback-classic</artifactId>
86+
<version>${logback.version}</version>
87+
</dependency>
7688
<dependency>
7789
<groupId>info.picocli</groupId>
7890
<artifactId>picocli</artifactId>
@@ -147,11 +159,6 @@
147159
<artifactId>maven-timeline</artifactId>
148160
<version>${maven-extensions-timeline.version}</version>
149161
</extension>
150-
<extension>
151-
<groupId>org.apache.maven.extensions</groupId>
152-
<artifactId>maven-build-cache-extension</artifactId>
153-
<version>${maven-extensions-build-cache.version}</version>
154-
</extension>
155162
</extensions>
156163

157164
<plugins>
@@ -354,7 +361,7 @@
354361
</filters>
355362
<transformers>
356363
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
357-
<mainClass>info.jab.core.PlantUMLToPng</mainClass>
364+
<mainClass>info.jab.core.MainApplication</mainClass>
358365
</transformer>
359366
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
360367
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"/>

src/main/java/info/jab/core/PlantUMLToPng.java renamed to src/main/java/info/jab/core/MainApplication.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.concurrent.Callable;
1010

1111
import org.jspecify.annotations.Nullable;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
1214

1315
import java.util.Objects;
1416
import java.util.Optional;
@@ -31,7 +33,9 @@
3133
mixinStandardHelpOptions = true,
3234
usageHelpAutoWidth = true
3335
)
34-
public class PlantUMLToPng implements Callable<Integer> {
36+
public class MainApplication implements Callable<Integer> {
37+
38+
private static final Logger logger = LoggerFactory.getLogger(MainApplication.class);
3539

3640
@Option(
3741
names = {"-f", "--file"},
@@ -54,7 +58,7 @@ public class PlantUMLToPng implements Callable<Integer> {
5458
/**
5559
* Default constructor for CLI usage.
5660
*/
57-
public PlantUMLToPng() {
61+
public MainApplication() {
5862
this.fileValidator = new PlantUMLFileValidator();
5963
this.fileService = new PlantUMLFileService();
6064
this.watchService = new PlantUMLWatchService(fileService);
@@ -67,7 +71,7 @@ public PlantUMLToPng() {
6771
* @param fileService The PlantUML file service to use
6872
* @param watchService The watch service to use
6973
*/
70-
public PlantUMLToPng(PlantUMLFileValidator fileValidator, PlantUMLFileService fileService, PlantUMLWatchService watchService) {
74+
public MainApplication(PlantUMLFileValidator fileValidator, PlantUMLFileService fileService, PlantUMLWatchService watchService) {
7175
this.fileValidator = fileValidator;
7276
this.fileService = fileService;
7377
this.watchService = watchService;
@@ -94,20 +98,20 @@ public CliResult execute() {
9498
return handleSingleFileConversion(inputFile);
9599
}
96100

97-
System.out.println("Use --help to see available options.");
101+
logger.info("Use --help to see available options.");
98102
return CliResult.KO;
99103
}
100104

101105
private CliResult handleSingleFileConversion(String inputFile) {
102106
if (Objects.isNull(inputFile)) {
103-
System.err.println("Error: No input file specified. Use -f or --file option.");
107+
logger.error("Error: No input file specified. Use -f or --file option.");
104108
return CliResult.KO;
105109
}
106110

107111
// Validate input file using PlantUMLFileValidator
108112
Optional<Path> validatedPath = fileValidator.validatePlantUMLFile(inputFile);
109113
if (validatedPath.isEmpty()) {
110-
System.err.println("Invalid PlantUML file: " + inputFile);
114+
logger.error("Invalid PlantUML file: {}", inputFile);
111115
return CliResult.KO;
112116
}
113117

@@ -125,19 +129,19 @@ private CliResult handleWatchMode(String parameter) {
125129
if (Objects.isNull(parameter) || parameter.trim().isEmpty()) {
126130
// Use current directory as default
127131
watchDirectory = Path.of(System.getProperty("user.dir"));
128-
System.out.println("Using current directory for watching: " + parameter);
132+
logger.info("Using current directory for watching: {}", parameter);
129133
} else {
130134
// Validate the provided directory
131135
watchDirectory = Path.of(parameter);
132136
if (!Files.exists(watchDirectory)) {
133-
System.err.println("Error: Directory does not exist: " + parameter);
137+
logger.error("Error: Directory does not exist: {}", parameter);
134138
return CliResult.KO;
135139
}
136140
if (!Files.isDirectory(watchDirectory)) {
137-
System.err.println("Error: Path is not a directory: " + parameter);
141+
logger.error("Error: Path is not a directory: {}", parameter);
138142
return CliResult.KO;
139143
}
140-
System.out.println("Using specified directory for watching: " + parameter);
144+
logger.info("Using specified directory for watching: {}", parameter);
141145
}
142146

143147
int watchResult = watchService.startWatching(watchDirectory);
@@ -147,10 +151,10 @@ private CliResult handleWatchMode(String parameter) {
147151
private static void printBanner() {
148152
try {
149153
System.out.println();
150-
String asciiArt = FigletFont.convertOneLine("PlantUML to PNG CLI");
154+
String asciiArt = FigletFont.convertOneLine("Puml to Png CLI");
151155
System.out.println(colorize(asciiArt, Attribute.GREEN_TEXT()));
152156
} catch (IOException e) {
153-
System.out.println("Error printing banner: " + e.getMessage());
157+
logger.error("Error printing banner: {}", e.getMessage());
154158
}
155159
}
156160

@@ -161,7 +165,7 @@ private static void printBanner() {
161165
*/
162166
public static void main(String[] args) {
163167
printBanner();
164-
PlantUMLToPng cli = new PlantUMLToPng();
168+
MainApplication cli = new MainApplication();
165169
int exitCode = new CommandLine(cli).execute(args);
166170
System.exit(exitCode);
167171
}

src/main/java/info/jab/core/PlantUMLFileService.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import java.util.Objects;
88
import java.util.Optional;
99

10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
1013
/**
1114
* Service class for handling PlantUML file processing operations.
1215
*
@@ -17,6 +20,8 @@
1720
*/
1821
public class PlantUMLFileService {
1922

23+
private static final Logger logger = LoggerFactory.getLogger(PlantUMLFileService.class);
24+
2025
private static final String DEFAULT_PLANTUML_SERVER = "http://www.plantuml.com/plantuml";
2126

2227
private final PlantUMLHttpClient httpClient;
@@ -56,10 +61,10 @@ public PlantUMLFileService(String plantUmlServerUrl) {
5661
public boolean processFile(Path inputPath) {
5762
Optional<Path> result = convertToPng(inputPath);
5863
if (result.isPresent()) {
59-
System.out.println("Successfully converted: " + inputPath + " -> " + result.get());
64+
logger.info("Successfully converted: {} -> {}", inputPath, result.get());
6065
return true;
6166
} else {
62-
System.err.println("Failed to convert file: " + inputPath);
67+
logger.error("Failed to convert file: {}", inputPath);
6368
return false;
6469
}
6570
}
@@ -71,7 +76,7 @@ public boolean processFile(Path inputPath) {
7176
* @return Optional containing the PNG output file path, or empty if conversion fails
7277
*/
7378
public Optional<Path> convertToPng(Path inputPath) {
74-
System.out.println("Converting PlantUML file to PNG: " + inputPath);
79+
logger.info("Converting PlantUML file to PNG: {}", inputPath);
7580
try {
7681
// Read file content
7782
String content = Files.readString(inputPath, StandardCharsets.UTF_8);
@@ -94,9 +99,7 @@ public Optional<Path> convertToPng(Path inputPath) {
9499
Files.write(outputPath, pngData.get());
95100

96101
return Optional.of(outputPath);
97-
98-
} catch (IOException | SecurityException e) {
99-
// Handle file I/O errors gracefully
102+
} catch (IOException e) {
100103
return Optional.empty();
101104
}
102105
}

src/main/java/info/jab/core/PlantUMLHttpClient.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import java.util.Optional;
1212
import java.util.zip.Deflater;
1313

14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
1417
/**
1518
* HTTP client for PlantUML server operations.
1619
*
@@ -20,6 +23,8 @@
2023
*/
2124
public class PlantUMLHttpClient {
2225

26+
private static final Logger logger = LoggerFactory.getLogger(PlantUMLHttpClient.class);
27+
2328
private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(10);
2429

2530
private final String serverUrl;
@@ -60,11 +65,16 @@ public Optional<byte[]> generatePngData(String plantUMLContent) {
6065
if (response.statusCode() == 200) {
6166
byte[] pngData = response.body();
6267
return pngData.length > 0 ? Optional.of(pngData) : Optional.empty();
68+
} else if (response.statusCode() == 400) {
69+
logger.error("Status code: {}", response.statusCode());
70+
byte[] pngData = response.body();
71+
return pngData.length > 0 ? Optional.of(pngData) : Optional.empty();
72+
} else {
73+
logger.error("Status code: {}", response.statusCode());
6374
}
64-
6575
return Optional.empty();
66-
6776
} catch (Exception e) {
77+
logger.error("Failed to generate PNG data. {}", e.getMessage());
6878
return Optional.empty();
6979
}
7080
}

src/main/java/info/jab/core/PlantUMLWatchService.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
import java.io.IOException;
44
import java.nio.file.Files;
55
import java.nio.file.Path;
6+
import java.nio.file.attribute.FileTime;
7+
import java.time.Instant;
8+
import java.time.temporal.ChronoUnit;
69
import java.util.List;
710
import java.util.Locale;
811
import java.util.Objects;
912
import java.util.function.BooleanSupplier;
1013

14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
1117
/**
1218
* Service responsible for watching directory changes and automatically converting PlantUML files.
1319
*
@@ -16,6 +22,8 @@
1622
*/
1723
public class PlantUMLWatchService {
1824

25+
private static final Logger logger = LoggerFactory.getLogger(PlantUMLWatchService.class);
26+
1927
private static final long DEFAULT_POLLING_INTERVAL_MS = 5000L;
2028
private static final String PUML_EXTENSION = ".puml";
2129
private static final String PNG_EXTENSION = ".png";
@@ -84,7 +92,7 @@ public void stopWatching() {
8492
* @return Exit code (0 for success, 1 for error)
8593
*/
8694
Integer startWatching(Path watchDirectory, BooleanSupplier shouldContinue) {
87-
System.out.println("Starting watch mode in directory: " + watchDirectory);
95+
logger.info("Starting watch mode in directory: {}", watchDirectory);
8896

8997
try {
9098
while (shouldContinue.getAsBoolean()) {
@@ -96,11 +104,11 @@ Integer startWatching(Path watchDirectory, BooleanSupplier shouldContinue) {
96104
return 0;
97105

98106
} catch (InterruptedException e) {
99-
System.out.println("Watch mode interrupted. Exiting...");
107+
logger.error("Watch mode interrupted. Exiting...");
100108
Thread.currentThread().interrupt();
101109
return 1;
102110
} catch (IOException e) {
103-
System.err.println("Error scanning directory for .puml files: " + e.getMessage());
111+
logger.error("Error scanning directory for .puml files: {}", e.getMessage());
104112
return 1;
105113
}
106114
}
@@ -120,10 +128,17 @@ void processPlantUMLFiles(Path directory) throws IOException {
120128
Path relativePath = directory.relativize(file);
121129
Path pngFile = getPngPath(file);
122130
boolean pngExists = Files.exists(pngFile);
131+
boolean isRecentlyModified = isFileModifiedInLastSeconds(file);
132+
133+
// Convert if PNG doesn't exist OR if PUML was modified in the last minute
134+
// OR if both files were modified in the same minute (to ensure synchronization)
135+
boolean bothFilesModifiedInSameMinute = pngExists && isRecentlyModified && !isFileModifiedInLastSeconds(pngFile);
123136

124-
// Only print and convert if the PNG file does not exist
125-
if (!pngExists) {
126-
System.out.println("Found: " + relativePath);
137+
// TODO: Technical debt.
138+
if (!pngExists || isRecentlyModified || bothFilesModifiedInSameMinute) {
139+
String reason = !pngExists ? "no .png exists" :
140+
bothFilesModifiedInSameMinute ? "recently modified .puml file" : "both files recently modified";
141+
logger.info("Found: {} ({})", relativePath, reason);
127142
convertToPng(file);
128143
}
129144
});
@@ -159,6 +174,24 @@ private Path getPngPath(Path pumlFile) {
159174
return Path.of(pngFileName);
160175
}
161176

177+
/**
178+
* Checks if a file was modified in the last minute.
179+
* Package-private to allow overriding in tests.
180+
*
181+
* @param filePath The path to the file to check
182+
* @return true if the file was modified in the last minute, false otherwise
183+
*/
184+
boolean isFileModifiedInLastSeconds(Path filePath) {
185+
try {
186+
FileTime lastModified = Files.getLastModifiedTime(filePath);
187+
Instant tenSecondsAgo = Instant.now().minus(10, ChronoUnit.SECONDS);
188+
return lastModified.toInstant().isAfter(tenSecondsAgo);
189+
} catch (IOException e) {
190+
logger.warn("Could not check modification time for file: {}", filePath, e);
191+
return false;
192+
}
193+
}
194+
162195
/**
163196
* Converts a PlantUML file to PNG format.
164197
*

0 commit comments

Comments
 (0)