Fix config loading with multiline strings, add small tests

Fixes #37
This commit is contained in:
KennyTV committed 2020-03-30 12:48:48 +02:00
1 parent 335fe303b5
commit eebbc4d532
6 files changed
+219 -25

No files matched your search

+3
View File
@@ -5,6 +5,9 @@ This file contains update logs for this project. The top may contain a `Unreleas
### Changed
* Added Chinese language file (thanks to Spigot user yeban)
### Fixed
* Fixed config parsing with multi line strings
## 3.0.4 - March 17th 2020
### Changed
* Bungee/Velocity: Added config option `waiting-server` to send players to a waiting server when global maintenance is enabled (instead of kicking them)
+2 -2
View File
@@ -1,5 +1,4 @@
.metadata
dependencies/
bin/
tmp/
*.tmp
@@ -92,4 +91,5 @@ $RECYCLE.BIN/
*.msm
*.msp
*.lnk
!MaintenanceAPI.jar
!MaintenanceAPI.jar
**/src/test/resources/
@@ -93,10 +93,16 @@ public final class Config extends ConfigSection {
}
public void save() throws IOException {
final byte[] bytes = this.saveConfigToString().getBytes(StandardCharsets.UTF_8);
this.file.getParentFile().mkdirs();
this.file.createNewFile();
Files.write(this.file.toPath(), bytes);
saveTo(file);
}
public void saveTo(final File file) throws IOException {
final byte[] bytes = toString().getBytes(StandardCharsets.UTF_8);
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
file.createNewFile();
Files.write(file.toPath(), bytes);
}
public boolean addMissingFields(final Map<String, Object> fields, final Map<String, String[]> comments) {
@@ -150,10 +156,6 @@ public final class Config extends ConfigSection {
return new Yaml(options);
}
public String saveConfigToString() {
return ConfigSerializer.serialize(this.header, this.values, this.comments, this.yaml);
}
public void clear() {
this.values.clear();
this.comments.clear();
@@ -176,4 +178,9 @@ public final class Config extends ConfigSection {
public void resetAwesomeHeader() {
this.header = AWESOME_HEADER;
}
@Override
public String toString() {
return ConfigSerializer.serialize(this.header, this.values, this.comments, this.yaml);
}
}
@@ -40,7 +40,9 @@ public final class ConfigSerializer {
private static final int INDENT_UNIT = 2;
public static String serialize(final String header, final Map<String, Object> data, final Map<String, String[]> comments, final Yaml yaml) {
if (data.isEmpty()) return yaml.dump(null);
if (data.isEmpty()) {
return yaml.dump(null);
}
final String rawYaml = yaml.dump(data);
final StringBuilder fileData = new StringBuilder();
@@ -61,46 +63,59 @@ public final class ConfigSerializer {
key = join(array, array.length - backspace);
keyLine = true;
} else {
keyLine = line.contains(":");
keyLine = line.indexOf(':') != -1;
}
if (keyLine) {
final String newKey = substring.split(Pattern.quote(":"))[0]; // Not sure about the quote thing, so I'll just keep it :aaa:
if (!key.isEmpty())
key += PATH_SEPARATOR_STRING;
key += newKey;
if (!keyLine) {
// Nothing to do, go to next line
fileData.append(line).append('\n');
continue;
}
final String newKey = substring.split(Pattern.quote(":"))[0]; // Not sure about the quote thing, so I'll just keep it :aaa:
if (!key.isEmpty()) {
key += PATH_SEPARATOR_STRING;
}
key += newKey;
// Add comments if present
if (comments != null) {
final String[] strings = comments.get(key);
if (strings != null) {
final String indentText = indent > 0 ? line.substring(0, indent) : "";
for (final String comment : strings) {
if (comment.isEmpty())
if (comment.isEmpty()) {
fileData.append('\n');
else
} else {
fileData.append(indentText).append(comment).append('\n');
}
}
}
currentKeyIndents = indents;
}
currentKeyIndents = indents;
fileData.append(line).append('\n');
}
return header != null && !header.isEmpty() ? header + fileData : fileData.toString();
}
public static Map<String, String[]> deserializeComments(final String data) {
//TODO go through known yaml keys and figure out comments from there instead of from line to line?
final Map<String, String[]> comments = new HashMap<>();
final List<String> currentComments = new ArrayList<>();
boolean header = true;
boolean multiLineValue = false;
int currentIndents = 0;
String key = "";
for (final String line : data.split("\n")) {
final String s = line.trim();
// It's a comment!
if (s.startsWith("#")) {
currentComments.add(s);
continue;
}
// Header is over - save it!
if (header) {
if (!currentComments.isEmpty()) {
currentComments.add("");
@@ -109,21 +124,39 @@ public final class ConfigSerializer {
}
header = false;
}
// Save empty lines as well
if (s.isEmpty()) {
currentComments.add(s);
continue;
}
// Multi line values?
if (s.startsWith("- |")) {
multiLineValue = true;
continue;
}
final int indent = getIndents(line);
final int indents = indent / INDENT_UNIT;
// Check if the multi line value is over
if (multiLineValue) {
if (indents > currentIndents) continue;
multiLineValue = false;
}
// Check if this is a level lower
if (indents <= currentIndents) {
final String[] array = key.split(PATH_SEPARATOR_QUOTED);
final int backspace = currentIndents - indents + 1;
key = join(array, array.length - backspace);
final int delta = array.length - backspace;
key = delta >= 0 ? join(array, delta) : key;
}
// Finish current key
final String separator = key.isEmpty() ? "" : PATH_SEPARATOR_STRING;
final String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
final String lineKey = line.indexOf(':') != -1 ? line.split(Pattern.quote(":"))[0] : line;
key += separator + lineKey.substring(indent);
currentIndents = indents;
@@ -138,8 +171,11 @@ public final class ConfigSerializer {
private static int getIndents(final String line) {
int count = 0;
for (int i = 0; i < line.length(); i++) {
if (line.charAt(i) == ' ') count++;
else break;
if (line.charAt(i) != ' ') {
break;
}
count++;
}
return count;
}
@@ -1,4 +1,91 @@
package eu.kennytv.maintenance.core.config;
import com.google.common.io.Files;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.io.File;
import java.io.IOException;
// I'm sorry in advance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ConfigTest {
@BeforeAll
void initAll() {
delete("config.yml");
delete("config-saved.yml");
delete("dirty-config-saved.yml");
delete("dirty-config-upgraded.yml");
copyOriginalToTest("config.yml", "config.yml");
}
@Test
void testSaveConsistency() throws IOException {
final Config config = new Config(getTestFile("config.yml"));
config.load();
final File saveTo = getTestFile("config-saved.yml");
config.saveTo(saveTo);
final Config newlyLoaded = new Config(saveTo);
newlyLoaded.load();
assert newlyLoaded.toString().equals(config.toString());
}
@Test
void testParsing() throws IOException {
final Config config = new Config(getTestFile("dirty-config.yml"));
config.load();
final File saveTo = getTestFile("dirty-config-saved.yml");
config.saveTo(saveTo);
}
@Test
void testUpgrading() throws IOException {
final Config config = new Config(getTestFile("dirty-config.yml"));
config.load();
final Config newestConfig = new Config(getTestFile("config.yml"));
newestConfig.load();
config.addMissingFields(newestConfig.getValues(), newestConfig.getComments());
config.set("config-version", 4);
final File saveTo = getTestFile("dirty-config-upgraded.yml");
config.resetAwesomeHeader();
config.saveTo(saveTo);
final Config newlyLoaded = new Config(saveTo);
newlyLoaded.load();
assert newlyLoaded.contains("waiting-server");
assert newlyLoaded.getComments().get("maintenance-enabled") != null;
assert newlyLoaded.getComments().get("mysql.use-ssl") != null;
assert newlyLoaded.getComments().get("update-checks") != null;
assert newlyLoaded.getHeader().equals(newestConfig.getHeader());
assert newlyLoaded.getSection("mysql").getString("username").equals("useer");
assert newlyLoaded.getInt("config-version") == 4;
}
private File getTestFile(final String path) {
return new File("src/test/resources/" + path);
}
private void copyOriginalToTest(final String from, final String to) {
final File file = new File("src/main/resources/" + from);
try {
Files.copy(file, getTestFile(to));
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
private void delete(final String path) {
final File file = getTestFile(path);
if (file.exists()) {
file.delete();
}
}
}
@@ -0,0 +1,61 @@
# Enables maintenance mode.
maintenance-enabled: true
# Enables maintenance on certain servers managed by your Bungee instance(s).
proxied-maintenance-servers:
- SpigotServer1
# - AnotherServer
# If maintenance is enabled on a proxied server with players on it,
# they will be sent to the fallback server instead of being kicked off the network.
# If the fallback server is not reachable/not set, they will just be kicked off the network.
fallback: ''
# The message shown in the multiplayer server list motd when maintenance is enabled.
# If you put in multiple entries, one of them will be chosen randomly on every ping.
# If running an endtimer, the time left can be displayed by including '%TIMER%' in a pingmessage (also works in playercount(-hover) message).
pingmessages:
- |2-
&2&lX's &b&lServer &f- &7&l(1.12.2-1.15.2)
&4&n&lMANUTENZIONE!
enable-timerspecific-messages: true
timerspecific-pingmessages:
- |2-
&2&lX's &b&lServer &f- &7&l(1.12.2-1.15.2)
&4&n&lMANUTENZIONE! Torneremo tra %TIMER%
enable-playercountmessage: true
playercountmessage: '&41.12.2-1.15.2'
playercounthovermessage: '&n&lStato server: In manutenzione'
custom-maintenance-icon: false
send-join-notification: false
kick-online-players: true
language: de
mysql:
use-mysql: false
host: host
port: 3306
database: database
username: useer
password: password
update-interval: 15
use-ssl: true
continue-endtimer-after-restart:
enabled: false
end: 0
timer-broadcast-for-seconds:
- 1200
- 900
- 600
- 300
- 120
- 60
- 30
- 20
- 10
- 5
- 4
- 3
- 2
- 1
update-checks: true
config-version: 3