-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f0969eb
commit d67d55e
Showing
22 changed files
with
1,214 additions
and
0 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/BlankLine.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
import java.io.IOException; | ||
import java.io.Writer; | ||
|
||
public record BlankLine(String content) implements PreProcessorLine { | ||
|
||
@Override | ||
public void append(Writer writer) throws IOException { | ||
writer.write(content); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/CommentLine.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
import java.io.IOException; | ||
import java.io.Writer; | ||
|
||
public class CommentLine implements PreProcessorLine { | ||
|
||
private final String indent; | ||
public final String value; | ||
|
||
public CommentLine(String indent, String value) { | ||
this.indent = indent; | ||
this.value = value; | ||
} | ||
|
||
@Override | ||
public void append(Writer writer) throws IOException { | ||
writer.write(indent); | ||
writer.write('#'); | ||
writer.write(value); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/EnvironmentComment.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
import java.util.Objects; | ||
|
||
public class EnvironmentComment { | ||
|
||
public final String variableName; | ||
private final String variableNameLower; | ||
|
||
public final String variableType; | ||
private final String variableTypeLower; | ||
|
||
public final CommentLine commentLine; | ||
|
||
public EnvironmentComment(String variableName, String variableType, CommentLine commentLine) { | ||
this.variableName = variableName; | ||
this.variableNameLower = variableName.toLowerCase(); | ||
this.variableType = variableType; | ||
this.variableTypeLower = variableType.toLowerCase(); | ||
this.commentLine = commentLine; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (!(o instanceof EnvironmentComment that)) return false; | ||
return Objects.equals(variableNameLower, that.variableNameLower) && Objects.equals(variableTypeLower, that.variableTypeLower); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(variableNameLower, variableTypeLower); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/InputConflict.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
public enum InputConflict { | ||
INVALID_KEY_CHARACTERS, | ||
BLANK_VALUE, | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/KeyValueLine.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.io.IOException; | ||
import java.io.Writer; | ||
import java.util.List; | ||
|
||
public class KeyValueLine implements PreProcessorLine { | ||
|
||
public final String content; | ||
public final String key; | ||
public final String value; | ||
public final List<CommentLine> attachedComments; | ||
|
||
// This line-number won't be synchronized when migrating - it's only supposed to be | ||
// accessed on the file migrating *from*, as to calculate reference-distance metrics | ||
public final int lineNumberAsRead; | ||
|
||
public @Nullable KeyValueLine previous; | ||
public @Nullable KeyValueLine next; | ||
|
||
public boolean hadBlankBefore; | ||
public boolean hadBlankAfter; | ||
|
||
public KeyValueLine( | ||
String content, String key, String value, | ||
List<CommentLine> attachedComments, | ||
int lineNumberAsRead | ||
) { | ||
this.content = content; | ||
this.key = key; | ||
this.value = value; | ||
this.attachedComments = attachedComments; | ||
this.lineNumberAsRead = lineNumberAsRead; | ||
} | ||
|
||
@Override | ||
public void append(Writer writer) throws IOException { | ||
for (var attachedComment : attachedComments) { | ||
attachedComment.append(writer); | ||
writer.write('\n'); | ||
} | ||
|
||
writer.write(content); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/PreProcessConflict.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
public enum PreProcessConflict { | ||
VARIABLE_NOT_FOUND, | ||
MALFORMED_TEMPORARY_VARIABLE | ||
} |
237 changes: 237 additions & 0 deletions
237
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/PreProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
import me.blvckbytes.bbconfigmapper.YamlConfig; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.yaml.snakeyaml.nodes.*; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class PreProcessor { | ||
|
||
@FunctionalInterface | ||
public interface ScalarNodeHandler { | ||
void handle(ScalarNode node) throws Exception; | ||
} | ||
|
||
private final Field scalarNodeValueField; | ||
|
||
public PreProcessor() throws Exception { | ||
this.scalarNodeValueField = ScalarNode.class.getDeclaredField("value"); | ||
this.scalarNodeValueField.setAccessible(true); | ||
} | ||
|
||
public String preProcess(String input, PreProcessorInput substitutions) { | ||
var result = new StringBuilder(); | ||
|
||
var contentBegin = -1; | ||
var substitutionBegin = -1; | ||
|
||
for (var i = 0; i < input.length(); ++i) { | ||
var currentChar = input.charAt(i); | ||
|
||
if (currentChar == '@' && i != input.length() - 1 && input.charAt(i + 1) == '{') { | ||
if (contentBegin >= 0) { | ||
result.append(input, contentBegin, i); | ||
contentBegin = -1; | ||
} | ||
|
||
substitutionBegin = i; | ||
continue; | ||
} | ||
|
||
if (substitutionBegin >= 0 && currentChar == '}') { | ||
var substitutionContent = input.substring(substitutionBegin + 2, i); | ||
var openingParenthesisIndex = substitutionContent.indexOf('('); | ||
var temporaryVariables = Map.<String, String>of(); | ||
|
||
if (openingParenthesisIndex >= 0) { | ||
var closingParenthesisIndex = substitutionContent.indexOf(')'); | ||
|
||
if (closingParenthesisIndex > openingParenthesisIndex) { | ||
var temporaryVariablesContent = substitutionContent.substring(openingParenthesisIndex + 1, closingParenthesisIndex); | ||
temporaryVariables = parseTemporaryVariables(temporaryVariablesContent, openingParenthesisIndex + 1); | ||
substitutionContent = substitutionContent.substring(0, openingParenthesisIndex).trim(); | ||
} | ||
} | ||
|
||
var substitutionKey = substitutionContent; | ||
String substitution = substitutions.getValue(substitutionKey); | ||
|
||
if (substitution == null) | ||
throw new PreProcessorException(substitutionBegin, PreProcessConflict.VARIABLE_NOT_FOUND); | ||
|
||
result.append(renderInterpolations(substitution, temporaryVariables)); | ||
substitutionBegin = -1; | ||
continue; | ||
} | ||
|
||
if (substitutionBegin >= 0) | ||
continue; | ||
|
||
if (contentBegin == -1) | ||
contentBegin = i; | ||
} | ||
|
||
if (contentBegin >= 0) | ||
result.append(input.substring(contentBegin)); | ||
|
||
return result.toString(); | ||
} | ||
|
||
public Map<String, String> parseTemporaryVariables(String input, int beginIndex) { | ||
var result = new HashMap<String, String>(); | ||
|
||
var assignments = input.split(";"); | ||
|
||
for (var assignment : assignments) { | ||
var assignmentParts = assignment.split("=", 2); | ||
|
||
if (assignmentParts.length != 2) | ||
throw new PreProcessorException(beginIndex, PreProcessConflict.MALFORMED_TEMPORARY_VARIABLE); | ||
|
||
var key = assignmentParts[0].strip().toLowerCase(); | ||
var value = assignmentParts[1].strip(); | ||
|
||
if (key.isBlank() || value.isBlank()) | ||
throw new PreProcessorException(beginIndex, PreProcessConflict.MALFORMED_TEMPORARY_VARIABLE); | ||
|
||
result.put(key, value); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
public void forEachScalarValue(YamlConfig config, ScalarNodeHandler handler) throws Exception { | ||
forEachScalarValue(config.getRootNode(), null, config.getExpressionMarkerSuffix(), handler); | ||
} | ||
|
||
public void setScalarValue(ScalarNode node, String value) throws Exception { | ||
this.scalarNodeValueField.set(node, value); | ||
} | ||
|
||
public void forEachScalarValue( | ||
Node valueNode, @Nullable ScalarNode keyNode, | ||
String expressionMarkerSuffix, | ||
ScalarNodeHandler handler | ||
) throws Exception { | ||
if (valueNode instanceof ScalarNode scalarNode) { | ||
handler.handle(scalarNode); | ||
|
||
if (keyNode != null && !keyNode.getValue().endsWith(expressionMarkerSuffix)) | ||
setScalarValue(keyNode, keyNode.getValue() + expressionMarkerSuffix); | ||
|
||
return; | ||
} | ||
|
||
if (valueNode instanceof MappingNode mappingNode) { | ||
for (var entry : mappingNode.getValue()) { | ||
if (!(entry.getKeyNode() instanceof ScalarNode currentKeyNode)) | ||
continue; | ||
|
||
forEachScalarValue( | ||
entry.getValueNode(), currentKeyNode, | ||
expressionMarkerSuffix, | ||
handler | ||
); | ||
} | ||
|
||
return; | ||
} | ||
|
||
if (valueNode instanceof SequenceNode sequenceNode) { | ||
for (var entry : sequenceNode.getValue()) { | ||
forEachScalarValue( | ||
entry, keyNode, | ||
expressionMarkerSuffix, | ||
handler | ||
); | ||
} | ||
} | ||
} | ||
|
||
private boolean isValidSubstitutionChar(char c) { | ||
return ( | ||
(c >= 'a' && c <= 'z') || | ||
(c >= '0' && c <= '9') || | ||
c == '_' || c == '-' | ||
); | ||
} | ||
|
||
public String renderInterpolations(String input, Map<String, String> temporaryVariables) { | ||
var result = new StringBuilder(); | ||
|
||
var stringBuffer = new StringBuilder(); | ||
var interpolationBuffer = new StringBuilder(); | ||
|
||
var wasPreviousCharBackslash = false; | ||
|
||
for (var i = 0; i < input.length(); ++i) { | ||
var currentChar = input.charAt(i); | ||
|
||
var isEscaped = wasPreviousCharBackslash; | ||
wasPreviousCharBackslash = currentChar == '\\'; | ||
|
||
if (currentChar == '{') { | ||
if (isEscaped) | ||
stringBuffer.setLength(stringBuffer.length() - 1); | ||
else { | ||
interpolationBuffer.append(currentChar); | ||
continue; | ||
} | ||
} | ||
|
||
if (!interpolationBuffer.isEmpty()) { | ||
if (currentChar != '}') { | ||
if (!isValidSubstitutionChar(currentChar)) { | ||
stringBuffer.append(interpolationBuffer); | ||
stringBuffer.append(currentChar); | ||
interpolationBuffer.setLength(0); | ||
continue; | ||
} | ||
|
||
interpolationBuffer.append(currentChar); | ||
continue; | ||
} | ||
|
||
if (!stringBuffer.isEmpty()) { | ||
if (!result.isEmpty()) | ||
result.append(" & "); | ||
|
||
result.append('"').append(stringBuffer).append('"').append(" & "); | ||
stringBuffer.setLength(0); | ||
} | ||
|
||
var substitution = interpolationBuffer.substring(1); // Leading '{' of begin-marker | ||
var temporaryValue = temporaryVariables.get(substitution.toLowerCase()); | ||
|
||
if (temporaryValue != null) | ||
substitution = temporaryValue; | ||
|
||
result.append(substitution); | ||
|
||
interpolationBuffer.setLength(0); | ||
continue; | ||
} | ||
|
||
if (currentChar == '"') | ||
stringBuffer.append('\\'); | ||
|
||
stringBuffer.append(currentChar); | ||
} | ||
|
||
// Unterminated interpolation - treat as string | ||
if (!interpolationBuffer.isEmpty()) | ||
stringBuffer.append(interpolationBuffer); | ||
|
||
if (!stringBuffer.isEmpty()) { | ||
if (!result.isEmpty()) | ||
result.append(" & "); | ||
|
||
result.append('"').append(stringBuffer).append('"'); | ||
} | ||
|
||
return result.toString(); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/me/blvckbytes/bbconfigmapper/preprocessor/PreProcessorException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package me.blvckbytes.bbconfigmapper.preprocessor; | ||
|
||
public class PreProcessorException extends RuntimeException { | ||
|
||
public final int charIndex; | ||
public final PreProcessConflict conflict; | ||
|
||
public PreProcessorException(int charIndex, PreProcessConflict conflict) { | ||
this.charIndex = charIndex; | ||
this.conflict = conflict; | ||
} | ||
} |
Oops, something went wrong.