Skip to content

Commit 906edb1

Browse files
committed
refactor: Remove JShell for custom resources post-processing
1 parent 508e53a commit 906edb1

File tree

15 files changed

+702
-190
lines changed

15 files changed

+702
-190
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
- Correction of various typos in rules documentations
15+
- Changes the file post-processing process to use Java code instead of a JShell script. This reduces build times and also allows for testing the process.
1516

1617
### Deleted
1718

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.greencodeinitiative.tools.exporter;
2+
3+
import org.greencodeinitiative.tools.exporter.infra.PrepareResources;
4+
5+
import java.nio.file.Path;
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
import static java.util.Collections.emptyList;
10+
import static java.util.Optional.empty;
11+
import static java.util.Optional.ofNullable;
12+
13+
public class Main implements Runnable {
14+
private final List<String> args;
15+
16+
public Main(String[] args) {
17+
this.args = ofNullable(args).map(List::of).orElse(emptyList());
18+
}
19+
20+
public static void main(String... args) {
21+
new Main(args).run();
22+
}
23+
24+
@Override
25+
public void run() {
26+
Path sourceDir = argAsPath(0, "sourceDir");
27+
Path targetDir = argAsPath(1, "targetDir");
28+
new PrepareResources(
29+
sourceDir,
30+
targetDir
31+
).run();
32+
}
33+
34+
private Optional<String> optionalArg(int index) {
35+
if (args.size() <= index) {
36+
return empty();
37+
}
38+
return Optional.of(args.get(index));
39+
}
40+
41+
private String arg(int index, String description) {
42+
if (args.size() <= index) {
43+
throw new IllegalArgumentException("argument " + (index + 1) + " is required: " + description);
44+
}
45+
return optionalArg(index).orElseThrow(() -> new IllegalArgumentException("argument " + (index + 1) + " is required: " + description));
46+
}
47+
48+
private Path argAsPath(int index, String description) {
49+
return Path.of(arg(index, description));
50+
}
51+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.greencodeinitiative.tools.exporter.domain;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.util.Optional;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
8+
9+
import static java.util.Optional.empty;
10+
import static java.util.Optional.of;
11+
12+
public class Rule {
13+
/**
14+
* Resources to include
15+
*/
16+
private static final Pattern TARGET_RESOURCES = Pattern.compile("^.*/(?<ruleKey>GCI\\d+)/(?<language>[^/]*)/.*\\.html$");
17+
18+
public static Optional<Rule> createFromHtmlDescription(Path htmlDescription) {
19+
final Matcher matcher = TARGET_RESOURCES.matcher(htmlDescription.toString().replace('\\', '/'));
20+
if (!matcher.find()) {
21+
return empty();
22+
}
23+
final String ruleKey = matcher.group("ruleKey");
24+
final Path metadata = htmlDescription.getParent().getParent().resolve(ruleKey + ".json");
25+
final Path specificMetadata = htmlDescription.getParent().resolve(ruleKey + ".json");
26+
27+
if (!Files.isRegularFile(htmlDescription) || !Files.isRegularFile(metadata)) {
28+
return empty();
29+
}
30+
31+
return of(new Rule(
32+
matcher.group("language"),
33+
htmlDescription,
34+
metadata,
35+
specificMetadata
36+
));
37+
}
38+
39+
private final String language;
40+
private final Path htmlDescription;
41+
private final Path metadata;
42+
private final Path specificMetadata;
43+
44+
public Rule(String language, Path htmlDescription, Path metadata, Path specificMetadata) {
45+
this.language = language;
46+
this.htmlDescription = htmlDescription;
47+
this.metadata = metadata;
48+
this.specificMetadata = specificMetadata;
49+
}
50+
51+
public Path getHtmlDescriptionTargetPath(Path targetDir) {
52+
return targetDir.resolve(language).resolve(htmlDescription.getFileName());
53+
}
54+
55+
public Path getMetadataTargetPath(Path targetDir) {
56+
return targetDir.resolve(language).resolve(metadata.getFileName());
57+
}
58+
59+
public String language() {
60+
return language;
61+
}
62+
63+
public Path htmlDescription() {
64+
return htmlDescription;
65+
}
66+
67+
public Path metadata() {
68+
return metadata;
69+
}
70+
71+
public Path specificMetadata() {
72+
return specificMetadata;
73+
}
74+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.greencodeinitiative.tools.exporter.infra;
2+
3+
import jakarta.json.Json;
4+
import jakarta.json.JsonMergePatch;
5+
import jakarta.json.JsonObject;
6+
import jakarta.json.JsonReader;
7+
import jakarta.json.JsonValue;
8+
import jakarta.json.JsonWriter;
9+
import org.greencodeinitiative.tools.exporter.domain.Rule;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.nio.file.StandardCopyOption;
15+
import java.util.List;
16+
import java.util.Objects;
17+
import java.util.Optional;
18+
import java.util.stream.Collectors;
19+
import java.util.stream.Stream;
20+
21+
import static java.lang.System.Logger.Level.DEBUG;
22+
23+
public class PrepareResources implements Runnable {
24+
private static final System.Logger LOGGER = System.getLogger("PrepareResources");
25+
26+
private final Path sourceDir;
27+
private final Path targetDir;
28+
29+
public static void main(String... args) {
30+
new PrepareResources(
31+
Path.of(Objects.requireNonNull(System.getProperty("sourceDir"), "system property: sourceDir")),
32+
Path.of(Objects.requireNonNull(System.getProperty("targetDir"), "system property: targetDir"))
33+
).run();
34+
}
35+
36+
public PrepareResources(Path sourceDir, Path targetDir) {
37+
this.sourceDir = sourceDir;
38+
this.targetDir = targetDir;
39+
}
40+
41+
@Override
42+
public void run() {
43+
getResourcesToCopy().forEach(rule -> {
44+
mergeOrCopyJsonMetadata(rule.metadata(), rule.specificMetadata(), rule.getMetadataTargetPath(targetDir));
45+
copyFile(rule.htmlDescription(), rule.getHtmlDescriptionTargetPath(targetDir));
46+
});
47+
}
48+
49+
private List<Rule> getResourcesToCopy() {
50+
try (Stream<Path> stream = Files.walk(sourceDir)) {
51+
return stream
52+
.filter(Files::isRegularFile)
53+
.map(Rule::createFromHtmlDescription)
54+
.filter(Optional::isPresent)
55+
.map(Optional::get)
56+
.collect(Collectors.toList());
57+
} catch (IOException e) {
58+
throw new IllegalStateException("Unable to read file", e);
59+
}
60+
}
61+
62+
private void mergeOrCopyJsonMetadata(Path source, Path merge, Path target) {
63+
try {
64+
Files.createDirectories(target.getParent());
65+
} catch (IOException e) {
66+
throw new ProcessException(e);
67+
}
68+
if (Files.isRegularFile(merge)) {
69+
mergeJsonFile(source, merge, target);
70+
} else {
71+
copyFile(source, target);
72+
}
73+
}
74+
75+
void mergeJsonFile(Path source, Path merge, Path target) {
76+
LOGGER.log(DEBUG, "Merge: {0} and {1} -> {2}", source, merge, target);
77+
78+
try (
79+
JsonReader sourceJsonReader = Json.createReader(Files.newBufferedReader(source));
80+
JsonReader mergeJsonReader = Json.createReader(Files.newBufferedReader(merge));
81+
JsonWriter resultJsonWriter = Json.createWriter(Files.newBufferedWriter(target));
82+
) {
83+
Files.createDirectories(target.getParent());
84+
85+
JsonObject sourceJson = sourceJsonReader.readObject();
86+
JsonObject mergeJson = mergeJsonReader.readObject();
87+
88+
JsonMergePatch mergePatch = Json.createMergePatch(mergeJson);
89+
JsonValue result = mergePatch.apply(sourceJson);
90+
91+
resultJsonWriter.write(result);
92+
} catch (IOException e) {
93+
throw new ProcessException("cannot process source " + source, e);
94+
}
95+
}
96+
97+
private void copyFile(Path source, Path target) {
98+
try {
99+
LOGGER.log(DEBUG, "Copy: {0} -> {1}", source, target);
100+
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
101+
} catch (IOException e) {
102+
throw new ProcessException("unable to copy '" + source + "' to '" + target + "'", e);
103+
}
104+
}
105+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.greencodeinitiative.tools.exporter.infra;
2+
3+
public class ProcessException extends RuntimeException {
4+
public ProcessException(Throwable cause) {
5+
super(cause);
6+
}
7+
8+
public ProcessException(String message, Throwable cause) {
9+
super(message, cause);
10+
}
11+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.greencodeinitiative.tools.exporter.domain;
2+
3+
import org.junit.jupiter.api.DisplayName;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.nio.file.Path;
7+
import java.util.Optional;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
12+
class RuleTest {
13+
private final Path htmlDescription = Path.of("some/path/file.html");
14+
private final Path metadata = Path.of("some/path/metadata.json");
15+
private final Path specificMetadata = Path.of("some/path/specificMetadata.json");
16+
private final Path targetDir = Path.of("target");
17+
18+
@Test
19+
@DisplayName("Should return an empty Optional when the path does not match the expected pattern")
20+
void createFromHtmlDescriptionShouldReturnEmptyWhenPathDoesNotMatchPattern() {
21+
Path invalidPath = Path.of("invalid/path/to/file.html");
22+
Optional<Rule> result = Rule.createFromHtmlDescription(invalidPath);
23+
assertTrue(result.isEmpty(), "Expected empty Optional for invalid path");
24+
}
25+
26+
@Test
27+
@DisplayName("Should return correct values for all getters")
28+
void gettersShouldReturnCorrectValues() {
29+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
30+
31+
assertEquals("java", rule.language());
32+
assertEquals(htmlDescription, rule.htmlDescription());
33+
assertEquals(metadata, rule.metadata());
34+
assertEquals(specificMetadata, rule.specificMetadata());
35+
}
36+
37+
@Test
38+
@DisplayName("Should return the correct target path for the HTML description")
39+
void getHtmlDescriptionTargetPathShouldReturnCorrectPath() {
40+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
41+
Path expectedPath = targetDir.resolve("java").resolve("file.html");
42+
43+
assertEquals(expectedPath, rule.getHtmlDescriptionTargetPath(targetDir));
44+
}
45+
46+
@Test
47+
@DisplayName("Should return the correct target path for the metadata file")
48+
void getMetadataTargetPathShouldReturnCorrectPath() {
49+
Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata);
50+
Path expectedPath = targetDir.resolve("java").resolve("metadata.json");
51+
52+
assertEquals(expectedPath, rule.getMetadataTargetPath(targetDir));
53+
}
54+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.greencodeinitiative.tools.exporter.infra;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.io.TempDir;
6+
7+
import java.io.IOException;
8+
import java.nio.file.Files;
9+
import java.nio.file.NoSuchFileException;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.catchThrowable;
15+
16+
class PrepareResourcesTest {
17+
private final static Path sourceDir = Paths.get("maven-build/test/resources");
18+
19+
@TempDir
20+
private Path targetDir;
21+
private PrepareResources prepareResources;
22+
23+
@BeforeEach
24+
void setUp() {
25+
prepareResources = new PrepareResources(sourceDir, targetDir);
26+
}
27+
28+
@Test
29+
void shouldCopyFileWhenNoMergeFile() {
30+
// When
31+
prepareResources.run();
32+
33+
// Then
34+
assertThat(targetDir.resolve("html/GCI36.json")).exists();
35+
assertThat(targetDir.resolve("html/GCI36.html")).exists();
36+
assertThat(targetDir.resolve("javascript/GCI36.json")).exists();
37+
assertThat(targetDir.resolve("javascript/GCI36.html")).exists();
38+
}
39+
40+
@Test
41+
void shouldThrowExceptionWhenSourceDirNotFound() {
42+
// Given
43+
Path invalid = Paths.get("not-existing-dir");
44+
PrepareResources pr = new PrepareResources(invalid, targetDir);
45+
46+
// When
47+
Throwable thrown = catchThrowable(pr::run);
48+
49+
// Then
50+
assertThat(thrown)
51+
.isInstanceOf(IllegalStateException.class)
52+
.hasMessage("Unable to read file")
53+
.hasCauseInstanceOf(NoSuchFileException.class)
54+
.hasRootCauseMessage("not-existing-dir");
55+
}
56+
57+
@Test
58+
void shouldMergeJsonFilesCorrectly(@TempDir Path tempDir) throws IOException {
59+
// Given
60+
Path sourceJson = Files.createFile(tempDir.resolve("source.json"));
61+
Path mergeJson = Files.createFile(tempDir.resolve("merge.json"));
62+
Path targetJson = tempDir.resolve("result.json");
63+
64+
String sourceContent = "{\n" +
65+
"\"key1\": \"value1\",\n" +
66+
"\"key2\": \"value2\"\n" +
67+
"}";
68+
String mergeContent = "{\n" +
69+
"\"key2\": \"newValue2\",\n" +
70+
"\"key3\": \"value3\"\n" +
71+
"}";
72+
73+
Files.writeString(sourceJson, sourceContent);
74+
Files.writeString(mergeJson, mergeContent);
75+
76+
// When
77+
prepareResources.mergeJsonFile(sourceJson, mergeJson, targetJson);
78+
79+
// Then
80+
String expectedContent = "{\n" +
81+
"\"key1\": \"value1\",\n" +
82+
"\"key2\": \"newValue2\",\n" +
83+
"\"key3\": \"value3\"\n" +
84+
"}";
85+
assertThat(targetJson).exists();
86+
assertThat(Files.readString(targetJson)).isEqualToIgnoringWhitespace(expectedContent);
87+
}
88+
}

maven-build/test/resources/desc.html

Whitespace-only changes.

maven-build/test/resources/meta.json

Whitespace-only changes.

0 commit comments

Comments
 (0)