Skip to content

Commit

Permalink
added commented-out key detection to the missing key extension process
Browse files Browse the repository at this point in the history
  • Loading branch information
BlvckBytes committed Sep 20, 2024
1 parent aa77fe2 commit b45ad7f
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 4 deletions.
8 changes: 7 additions & 1 deletion README-X.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,10 @@ position.

A *YamlConfig* supports the extension of missing keys from another instance, which is a way to migrate existing configuration files
to a newer version by adding new key-value pairs. Existing sections are extended, missing sections are added. Keys are never deleted,
as that could possibly delete still needed configuration information for the user.
as that could possibly delete still needed configuration information for the user.

### Commented-Out Keys

Keys which are commented out are detected if their corresponding comment is within the right mapping-block and starts with
any amount of whitespace, followed by the key's name, followed by a colon `:`. Since it is a feature of this mapper to comment
out keys in order to cause null-values in Sections, in order to thereby deactivate messages/features, these keys will not be extended.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ An object mapper for key-based configurations making use of [GPEEE](https://gith
- [Value Interpretation](#value-interpretation)
- [Comments](#comments)
- [Key Extension](#key-extension)
- [Commented-Out Keys](#commented-out-keys)

## Merging

Expand Down Expand Up @@ -94,4 +95,10 @@ position.

A *YamlConfig* supports the extension of missing keys from another instance, which is a way to migrate existing configuration files
to a newer version by adding new key-value pairs. Existing sections are extended, missing sections are added. Keys are never deleted,
as that could possibly delete still needed configuration information for the user.
as that could possibly delete still needed configuration information for the user.

### Commented-Out Keys

Keys which are commented out are detected if their corresponding comment is within the right mapping-block and starts with
any amount of whitespace, followed by the key's name, followed by a colon `:`. Since it is a feature of this mapper to comment
out keys in order to cause null-values in Sections, in order to thereby deactivate messages/features, these keys will not be extended.
98 changes: 96 additions & 2 deletions src/main/java/me/blvckbytes/bbconfigmapper/YamlConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.nodes.*;
import org.yaml.snakeyaml.representer.Representer;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -271,6 +273,86 @@ private void executeWhileMergedTuplesAbsent(Runnable executable) {
}
}

private @Nullable NodeTuple findTupleNodeRecursively(
Node node,
Predicate<NodeTuple> matchPredicate,
Predicate<MappingNode> skipPredicate
) {
NodeTuple result;

if (node instanceof MappingNode) {
if (skipPredicate.test((MappingNode) node))
return null;

for (NodeTuple entry : ((MappingNode) node).getValue()) {
if (matchPredicate.test(entry))
return entry;

if ((result = findTupleNodeRecursively(entry.getValueNode(), matchPredicate, skipPredicate)) != null)
return result;
}
}

else if (node instanceof SequenceNode) {
for (Node entry : ((SequenceNode) node).getValue()) {
if ((result = findTupleNodeRecursively(entry, matchPredicate, skipPredicate)) != null)
return result;
}
}

return null;
}

private boolean isKeyCommentedOut(String key, Node container) {
// BlockComment-s are members to their successive nodes
// For mappings, in the list of tuples, on the ScalarNode-s of the keys
// For lists, on the ScalarNode-s of items
// If a comment is at the end of the file, it'll be an EndComment on the root MappingNode

Predicate<CommentLine> commentedKeyMatcher = (comment) -> comment.getValue().trim().startsWith(key + ": ");

// Possibly, a key which is not the last has been commented out
if (container instanceof MappingNode) {
for (NodeTuple containerTuple : ((MappingNode) container).getValue()) {
if (containerTuple.getKeyNode().getBlockComments().stream().anyMatch(commentedKeyMatcher))
return true;
}
}

// Possibly, the last key has been commented out

Mark containerStart = container.getStartMark();

if (containerStart != null) {
// Find the first node that's after the container and either on same indent or less (maybe
// that's redundant) and skip over recursing down the current container.
NodeTuple nextTuple = findTupleNodeRecursively(
rootNode,
currentNode -> {
Mark otherStart = currentNode.getKeyNode().getStartMark();

return (
otherStart.getLine() > containerStart.getLine() &&
otherStart.getColumn() <= containerStart.getColumn()
);
},
currentContainer -> currentContainer == container
);

// Next node available - look at the next node's comments
if (nextTuple != null) {
if (nextTuple.getKeyNode().getBlockComments().stream().anyMatch(commentedKeyMatcher))
return true;
}
}

// No next-node available (EOF) - wrap around to root node

List<CommentLine> endComments = rootNode.getEndComments();

return endComments != null && endComments.stream().anyMatch(commentedKeyMatcher);
}

/**
* Extends keys which the provided config contains but are absent on this instance
* by copying over the values those keys hold
Expand All @@ -285,9 +367,19 @@ public int extendMissingKeys(YamlConfig other) {
if (this.exists(pathOfTuple))
return false;

String key = ((ScalarNode) tuple.getKeyNode()).getValue();
int lastPathDotIndex = pathOfTuple.lastIndexOf('.');

if (lastPathDotIndex > 0) {
String parentPath = pathOfTuple.substring(0, lastPathDotIndex);
Node parentNode = locateNode(parentPath, false, false).a;

if (isKeyCommentedOut(key, parentNode))
return false;
}

MappingNode container = locateContainerNode(pathOfTuple, true).a;
List<NodeTuple> containerTuples = container.getValue();
String key = ((ScalarNode) tuple.getKeyNode()).getValue();

// The new key is at an index which doesn't yet exist, add to the end of the tuple list
if (indexOfTuple >= containerTuples.size()) {
Expand Down Expand Up @@ -647,8 +739,10 @@ private NodeTuple createNewTuple(@Nullable Node keyNode, @Nullable String key, N
// Try to reuse already present key nodes
Node tupleKey = keyValueTuple == null ? null : keyValueTuple.getKeyNode();

List<NodeTuple> mappingTuples = mapping.getValue();
mappingTuples.remove(keyValueTuple);
keyValueTuple = createNewTuple(tupleKey, pathPart, createNewMappingNode(null));
mapping.getValue().add(keyValueTuple);
mappingTuples.add(keyValueTuple);

// Invalidate the (null) cache for this newly added tuple
invalidateLocateKeyCacheFor(mapping, pathPart);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,18 @@ public void shouldExtendKeysWithScalarValues() throws Exception {

helper.assertSave("mappings_patched.yml", baseConfig);
}

@Test
public void shouldNotExtendCommentedKeys() throws Exception {
char[] caseSuffixes = {'a', 'b', 'c'};

for (char caseSuffix : caseSuffixes) {
YamlConfig baseConfig = helper.makeConfig("commented_keys_base_" + caseSuffix + ".yml");
YamlConfig extensionConfig = helper.makeConfig("commented_keys_extension.yml");

baseConfig.extendMissingKeys(extensionConfig);

helper.assertSave("commented_keys_save_" + caseSuffix + ".yml", baseConfig);
}
}
}
8 changes: 8 additions & 0 deletions src/test/resources/commented_keys_base_a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
# e: 4
f: 5
g:
h: 2
8 changes: 8 additions & 0 deletions src/test/resources/commented_keys_base_b.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
# e: 4
f: 5
g:
h: 2
8 changes: 8 additions & 0 deletions src/test/resources/commented_keys_base_c.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
e: 4
f: 5
g:
# h: 2
8 changes: 8 additions & 0 deletions src/test/resources/commented_keys_extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
e: 4
f: 5
g:
h: 2
8 changes: 8 additions & 0 deletions src/test/resources/save/commented_keys_save_a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
# e: 4
f: 5
g:
h: 2
8 changes: 8 additions & 0 deletions src/test/resources/save/commented_keys_save_b.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
# e: 4
f: 5
g:
h: 2
8 changes: 8 additions & 0 deletions src/test/resources/save/commented_keys_save_c.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a:
b: 1
c: 2
d: 3
e: 4
f: 5
g:
# h: 2

0 comments on commit b45ad7f

Please sign in to comment.