introduces custom settings file (#1158)
* Introducing a custom settings file * formats * chnages * Update README.md
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -26,12 +26,12 @@ public class ConfigInitializer
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
try {
|
||||
ensureConfigExists();
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize application configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureConfigExists() throws IOException {
|
||||
public void ensureConfigExists() throws IOException, URISyntaxException {
|
||||
// Define the path to the external config directory
|
||||
Path destPath = Paths.get("configs", "settings.yml");
|
||||
|
||||
@@ -51,170 +51,132 @@ public class ConfigInitializer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If user file exists, we need to merge it with the template from the classpath
|
||||
List<String> templateLines;
|
||||
try (InputStream in =
|
||||
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||
templateLines =
|
||||
new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
Path templatePath =
|
||||
Paths.get(
|
||||
getClass()
|
||||
.getClassLoader()
|
||||
.getResource("settings.yml.template")
|
||||
.toURI());
|
||||
Path userPath = Paths.get("configs", "settings.yml");
|
||||
|
||||
mergeYamlFiles(templateLines, destPath, destPath);
|
||||
List<String> templateLines = Files.readAllLines(templatePath);
|
||||
List<String> userLines =
|
||||
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
||||
|
||||
Map<String, String> templateEntries = extractEntries(templateLines);
|
||||
Map<String, String> userEntries = extractEntries(userLines);
|
||||
|
||||
List<String> mergedLines = mergeConfigs(templateLines, templateEntries, userEntries);
|
||||
mergedLines = cleanInvalidYamlEntries(mergedLines);
|
||||
Files.write(userPath, mergedLines);
|
||||
}
|
||||
|
||||
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
|
||||
if (!Files.exists(customSettingsPath)) {
|
||||
Files.createFile(customSettingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeYamlFiles(List<String> templateLines, Path userFilePath, Path outputPath)
|
||||
throws IOException {
|
||||
List<String> userLines = Files.readAllLines(userFilePath);
|
||||
private static Map<String, String> extractEntries(List<String> lines) {
|
||||
Map<String, String> entries = new HashMap<>();
|
||||
String keyRegex = "^\\s*(\\w+)\\s*:\\s*(.*)"; // Capture key and value
|
||||
Pattern pattern = Pattern.compile(keyRegex);
|
||||
|
||||
for (String line : lines) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find() && !line.trim().startsWith("#")) {
|
||||
String key = matcher.group(1).trim();
|
||||
String value = matcher.group(2).trim(); // Capture the value directly
|
||||
entries.put(key, line);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
private static List<String> mergeConfigs(
|
||||
List<String> templateLines,
|
||||
Map<String, String> templateEntries,
|
||||
Map<String, String> userEntries) {
|
||||
List<String> mergedLines = new ArrayList<>();
|
||||
boolean insideAutoGenerated = false;
|
||||
boolean beforeFirstKey = true;
|
||||
|
||||
Function<String, Boolean> isCommented = line -> line.trim().startsWith("#");
|
||||
Function<String, String> extractKey =
|
||||
line -> {
|
||||
String[] parts = line.split(":");
|
||||
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
|
||||
};
|
||||
|
||||
Function<String, Integer> getIndentationLevel =
|
||||
line -> {
|
||||
int count = 0;
|
||||
for (char ch : line.toCharArray()) {
|
||||
if (ch == ' ') count++;
|
||||
else break;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
|
||||
Set<String> handledKeys = new HashSet<>();
|
||||
|
||||
for (String line : templateLines) {
|
||||
String key = extractKey.apply(line);
|
||||
|
||||
if ("AutomaticallyGenerated:".equalsIgnoreCase(line.trim())) {
|
||||
insideAutoGenerated = true;
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
} else if (insideAutoGenerated && line.trim().isEmpty()) {
|
||||
insideAutoGenerated = false;
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
|
||||
// Handle top comments and empty lines before the first key.
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key.isEmpty()) beforeFirstKey = false;
|
||||
|
||||
if (userKeys.contains(key)) {
|
||||
// If user has any version (commented or uncommented) of this key, skip the
|
||||
// template line
|
||||
Optional<String> userValue =
|
||||
userLines.stream()
|
||||
.filter(
|
||||
l ->
|
||||
extractKey.apply(l).equalsIgnoreCase(key)
|
||||
&& !isCommented.apply(l))
|
||||
.findFirst();
|
||||
if (userValue.isPresent()) mergedLines.add(userValue.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
|
||||
mergedLines.add(
|
||||
line); // If line is commented, empty or key not present in user's file,
|
||||
// retain the
|
||||
// template line
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add any additional uncommented user lines that are not present in the
|
||||
// template
|
||||
for (String userLine : userLines) {
|
||||
String userKey = extractKey.apply(userLine);
|
||||
boolean isPresentInTemplate =
|
||||
templateLines.stream()
|
||||
.map(extractKey)
|
||||
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
|
||||
if (!isPresentInTemplate && !isCommented.apply(userLine)) {
|
||||
if (!childOfTemplateEntry(
|
||||
isCommented,
|
||||
extractKey,
|
||||
getIndentationLevel,
|
||||
userLines,
|
||||
userLine,
|
||||
templateLines)) {
|
||||
// check if userLine is a child of a entry within templateLines or not, if child
|
||||
// of parent in templateLines then dont add to mergedLines, if anything else
|
||||
// then add
|
||||
mergedLines.add(userLine);
|
||||
String cleanLine = line.split("#")[0].trim();
|
||||
if (!cleanLine.isEmpty() && cleanLine.contains(":")) {
|
||||
String key = cleanLine.split(":")[0].trim();
|
||||
if (userEntries.containsKey(key)) {
|
||||
// Always use user's entry if exists
|
||||
mergedLines.add(userEntries.get(key));
|
||||
handledKeys.add(key);
|
||||
} else {
|
||||
// Use template's entry if no user entry
|
||||
mergedLines.add(line);
|
||||
}
|
||||
} else {
|
||||
// Add comments and other lines directly
|
||||
mergedLines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
|
||||
// Add user entries not present in the template at the end
|
||||
for (String key : userEntries.keySet()) {
|
||||
if (!handledKeys.contains(key)) {
|
||||
mergedLines.add(userEntries.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return mergedLines;
|
||||
}
|
||||
|
||||
// New method to check if a userLine is a child of an entry in templateLines
|
||||
boolean childOfTemplateEntry(
|
||||
Function<String, Boolean> isCommented,
|
||||
Function<String, String> extractKey,
|
||||
Function<String, Integer> getIndentationLevel,
|
||||
List<String> userLines,
|
||||
String userLine,
|
||||
List<String> templateLines) {
|
||||
String userKey = extractKey.apply(userLine).trim();
|
||||
int userIndentation = getIndentationLevel.apply(userLine);
|
||||
|
||||
// Start by assuming the line is not a child of an entry in templateLines
|
||||
boolean isChild = false;
|
||||
private static List<String> cleanInvalidYamlEntries(List<String> lines) {
|
||||
List<String> cleanedLines = new ArrayList<>();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
String trimmedLine = line.trim();
|
||||
|
||||
// Iterate backwards through userLines from the current line to find any parent
|
||||
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
|
||||
String potentialParentLine = userLines.get(i);
|
||||
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
|
||||
|
||||
// Check if we've reached a potential parent based on indentation
|
||||
if (parentIndentation < userIndentation) {
|
||||
String parentKey = extractKey.apply(potentialParentLine).trim();
|
||||
|
||||
// Now, check if this potential parent or any of its parents exist in templateLines
|
||||
boolean parentExistsInTemplate =
|
||||
templateLines.stream()
|
||||
.filter(line -> !isCommented.apply(line)) // Skip commented lines
|
||||
.anyMatch(
|
||||
templateLine -> {
|
||||
String templateKey =
|
||||
extractKey.apply(templateLine).trim();
|
||||
return parentKey.equalsIgnoreCase(templateKey);
|
||||
});
|
||||
|
||||
if (!parentExistsInTemplate) {
|
||||
// If the parent does not exist in template, check the next level parent
|
||||
userIndentation =
|
||||
parentIndentation; // Update userIndentation to the parent's indentation
|
||||
// for next iteration
|
||||
if (parentIndentation == 0) {
|
||||
// If we've reached the top-level parent and it's not in template, the
|
||||
// original line is considered not a child
|
||||
isChild = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// If any parent exists in template, the original line is considered a child
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
// Ignore commented lines and lines that don't look like key-only entries
|
||||
if (trimmedLine.startsWith("#")
|
||||
|| !trimmedLine.endsWith(":")
|
||||
|| trimmedLine.contains(" ")) {
|
||||
cleanedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// For potential key-only lines, check the next line to determine context
|
||||
if (isKeyWithoutChildrenOrValue(i, lines)) {
|
||||
// Skip adding the current line since it's a key without any following value or
|
||||
// children
|
||||
continue;
|
||||
}
|
||||
|
||||
cleanedLines.add(line);
|
||||
}
|
||||
return cleanedLines;
|
||||
}
|
||||
|
||||
private static boolean isKeyWithoutChildrenOrValue(int currentIndex, List<String> lines) {
|
||||
if (currentIndex + 1 < lines.size()) {
|
||||
String currentLine = lines.get(currentIndex);
|
||||
String nextLine = lines.get(currentIndex + 1);
|
||||
int currentIndentation = getIndentationLevel(currentLine);
|
||||
int nextIndentation = getIndentationLevel(nextLine);
|
||||
|
||||
// If the next line is less or equally indented, it's not a child or value
|
||||
return nextIndentation <= currentIndentation;
|
||||
}
|
||||
|
||||
return isChild; // Return true if the line is not a child of any entry in templateLines
|
||||
// If it's the last line, then it definitely has no children or value
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int getIndentationLevel(String line) {
|
||||
int count = 0;
|
||||
for (char ch : line.toCharArray()) {
|
||||
if (ch == ' ') count++;
|
||||
else break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user