Fixup config comment saving

Fixes #3052, #2536
This commit is contained in:
Nassim Jahnke 2022-08-01 11:34:21 +02:00
parent 087bbdce1f
commit f9bbb5b1a3
No known key found for this signature in database
GPG Key ID: 6BE3B555EBC5982B
3 changed files with 157 additions and 94 deletions

View File

@ -18,8 +18,6 @@
package com.viaversion.viaversion.util; package com.viaversion.viaversion.util;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.common.io.Files; import com.google.common.io.Files;
@ -28,19 +26,23 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class CommentStore { public class CommentStore {
private final Map<String, List<String>> headers = Maps.newConcurrentMap(); private final Map<String, List<String>> headers = new HashMap<>();
private final char pathSeperator; private final String pathSeparator;
private final String pathSeparatorQuoted;
private final int indents; private final int indents;
private List<String> mainHeader = Lists.newArrayList(); private List<String> mainHeader = new ArrayList<>();
public CommentStore(char pathSeperator, int indents) { public CommentStore(final char pathSeparator, final int indents) {
this.pathSeperator = pathSeperator; this.pathSeparator = Character.toString(pathSeparator);
this.pathSeparatorQuoted = Pattern.quote(this.pathSeparator);
this.indents = indents; this.indents = indents;
} }
@ -49,7 +51,7 @@ public class CommentStore {
* *
* @param header header * @param header header
*/ */
public void mainHeader(String... header) { public void mainHeader(final String... header) {
mainHeader = Arrays.asList(header); mainHeader = Arrays.asList(header);
} }
@ -68,8 +70,7 @@ public class CommentStore {
* @param key of option (or section) * @param key of option (or section)
* @param header of option (or section) * @param header of option (or section)
*/ */
public void header(String key, String... header) { public void header(final String key, final String... header) {
// String value = Joiner.on('\n').join(header);
headers.put(key, Arrays.asList(header)); headers.put(key, Arrays.asList(header));
} }
@ -79,126 +80,181 @@ public class CommentStore {
* @param key of option (or section) * @param key of option (or section)
* @return Header * @return Header
*/ */
public List<String> header(String key) { public List<String> header(final String key) {
return headers.get(key); return headers.get(key);
} }
public void storeComments(InputStream inputStream) throws IOException { public void storeComments(final InputStream inputStream) throws IOException {
InputStreamReader reader = new InputStreamReader(inputStream); final String data;
String contents; try (final InputStreamReader reader = new InputStreamReader(inputStream)) {
try { data = CharStreams.toString(reader);
contents = CharStreams.toString(reader);
} finally {
reader.close();
} }
StringBuilder memoryData = new StringBuilder();
// Parse headers final List<String> currentComments = new ArrayList<>();
final String pathSeparator = Character.toString(this.pathSeperator); boolean header = true;
boolean multiLineValue = false;
int currentIndents = 0; int currentIndents = 0;
String key = ""; String key = "";
List<String> headers = Lists.newArrayList(); for (final String line : data.split("\n")) {
for (String line : contents.split("\n")) { final String s = line.trim();
if (line.isEmpty()) continue; // Skip empty lines // It's a comment!
int indent = getSuccessiveCharCount(line, ' '); if (s.startsWith("#")) {
String subline = indent > 0 ? line.substring(indent) : line; currentComments.add(s);
if (subline.startsWith("#")) {
if (subline.startsWith("#>")) {
String txt = subline.startsWith("#> ") ? subline.substring(3) : subline.substring(2);
mainHeader.add(txt);
continue; // Main header, handled by bukkit
}
// Add header to list
String txt = subline.startsWith("# ") ? subline.substring(2) : subline.substring(1);
headers.add(txt);
continue; continue;
} }
int indents = indent / this.indents; // Header is over - save it!
if (indents <= currentIndents) { if (header) {
// Remove last section of key if (!currentComments.isEmpty()) {
String[] array = key.split(Pattern.quote(pathSeparator)); currentComments.add("");
int backspace = currentIndents - indents + 1; mainHeader.addAll(currentComments);
key = join(array, this.pathSeperator, 0, array.length - backspace); currentComments.clear();
}
header = false;
} }
// Add new section to key // Save empty lines as well
String separator = key.length() > 0 ? pathSeparator : ""; if (s.isEmpty()) {
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; currentComments.add(s);
key += separator + lineKey.substring(indent); continue;
}
// Multi line values?
if (s.startsWith("- |")) {
multiLineValue = true;
continue;
}
final int indent = getIndents(line);
final int indents = indent / this.indents;
// 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(pathSeparatorQuoted);
final int backspace = currentIndents - indents + 1;
final int delta = array.length - backspace;
key = delta >= 0 ? join(array, delta) : key;
}
// Finish current key
final String separator = key.isEmpty() ? "" : this.pathSeparator;
final String lineKey = line.indexOf(':') != -1 ? line.split(Pattern.quote(":"))[0] : line;
key += separator + lineKey.substring(indent);
currentIndents = indents; currentIndents = indents;
memoryData.append(line).append('\n'); if (!currentComments.isEmpty()) {
if (!headers.isEmpty()) { headers.put(key, new ArrayList<>(currentComments));
this.headers.put(key, headers); currentComments.clear();
headers = Lists.newArrayList();
} }
} }
} }
public void writeComments(String yaml, File output) throws IOException { public void writeComments(final String rawYaml, final File output) throws IOException {
// Custom save final StringBuilder fileData = new StringBuilder();
final int indentLength = this.indents; for (final String mainHeaderLine : mainHeader) {
final String pathSeparator = Character.toString(this.pathSeperator); fileData.append(mainHeaderLine).append('\n');
StringBuilder fileData = new StringBuilder();
int currentIndents = 0;
String key = "";
for (String h : mainHeader) {
// Append main header to top of file
fileData.append("#> ").append(h).append('\n');
} }
for (String line : yaml.split("\n")) { // Remove last new line
if (line.isEmpty() || line.trim().charAt(0) == '-') { fileData.deleteCharAt(fileData.length() - 1);
int currentKeyIndents = 0;
String key = "";
for (final String line : rawYaml.split("\n")) {
if (line.isEmpty()) {
continue;
}
final int indent = getIndents(line);
final int indents = indent / this.indents;
final boolean keyLine;
final String substring = line.substring(indent);
if (substring.trim().isEmpty() || substring.charAt(0) == '-') {
keyLine = false;
} else if (indents <= currentKeyIndents) {
final String[] array = key.split(this.pathSeparatorQuoted);
final int backspace = currentKeyIndents - indents + 1;
key = join(array, array.length - backspace);
keyLine = true;
} else {
keyLine = line.indexOf(':') != -1;
}
if (!keyLine) {
// Nothing to do, go to next line
fileData.append(line).append('\n'); fileData.append(line).append('\n');
continue; continue;
} }
int indent = getSuccessiveCharCount(line, ' '); final String newKey = substring.split(Pattern.quote(":"))[0]; // Not sure about the quote thing, so I'll just keep it :aaa:
int indents = indent / indentLength; if (!key.isEmpty()) {
String indentText = indent > 0 ? line.substring(0, indent) : ""; key += this.pathSeparator;
if (indents <= currentIndents) { }
// Remove last section of key key += newKey;
String[] array = key.split(Pattern.quote(pathSeparator));
int backspace = currentIndents - indents + 1; // Add comments
key = join(array, this.pathSeperator, 0, array.length - backspace); final List<String> strings = headers.get(key);
if (strings != null && !strings.isEmpty()) {
final String indentText = indent > 0 ? line.substring(0, indent) : "";
for (final String comment : strings) {
if (comment.isEmpty()) {
fileData.append('\n');
} else {
fileData.append(indentText).append(comment).append('\n');
}
}
} }
// Add new section to key currentKeyIndents = indents;
String separator = !key.isEmpty() ? pathSeparator : ""; fileData.append(line).append('\n');
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
key += separator + lineKey.substring(indent);
currentIndents = indents;
List<String> header = headers.get(key);
String headerText = header != null ? addHeaderTags(header, indentText) : "";
fileData.append(headerText).append(line).append('\n');
} }
// Write data to file // Write data to file
Files.write(fileData.toString(), output, StandardCharsets.UTF_8); Files.write(fileData.toString(), output, StandardCharsets.UTF_8);
} }
private String addHeaderTags(List<String> header, String indent) { private String addHeaderTags(final List<String> header, final String indent) {
StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
for (String line : header) { for (final String line : header) {
builder.append(indent).append("# ").append(line).append('\n'); builder.append(indent).append("# ").append(line).append('\n');
} }
return builder.toString(); return builder.toString();
} }
private String join(String[] array, char joinChar, int start, int length) { private String join(final String[] array, final char joinChar, final int start, final int length) {
String[] copy = new String[length - start]; final String[] copy = new String[length - start];
System.arraycopy(array, start, copy, 0, length - start); System.arraycopy(array, start, copy, 0, length - start);
return Joiner.on(joinChar).join(copy); return Joiner.on(joinChar).join(copy);
} }
private int getSuccessiveCharCount(String text, char key) { private int getIndents(final String line) {
int count = 0;
for (int i = 0; i < line.length(); i++) {
if (line.charAt(i) != ' ') {
break;
}
count++;
}
return count;
}
private String join(final String[] array, final int length) {
final String[] copy = new String[length];
System.arraycopy(array, 0, copy, 0, length);
return String.join(this.pathSeparator, copy);
}
private int getSuccessiveSpaces(final String text) {
int count = 0; int count = 0;
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == key) { if (text.charAt(i) == ' ') {
count += 1; count += 1;
} else { } else {
break; break;

View File

@ -62,9 +62,12 @@ public abstract class Config implements ConfigurationProvider {
return getClass().getClassLoader().getResource("assets/viaversion/config.yml"); return getClass().getClassLoader().getResource("assets/viaversion/config.yml");
} }
public synchronized Map<String, Object> loadConfig(File location) { public Map<String, Object> loadConfig(File location) {
return loadConfig(location, getDefaultConfigURL());
}
public synchronized Map<String, Object> loadConfig(File location, URL jarConfigFile) {
List<String> unsupported = getUnsupportedOptions(); List<String> unsupported = getUnsupportedOptions();
URL jarConfigFile = getDefaultConfigURL();
try { try {
commentStore.storeComments(jarConfigFile.openStream()); commentStore.storeComments(jarConfigFile.openStream());
for (String option : unsupported) { for (String option : unsupported) {
@ -137,6 +140,10 @@ public abstract class Config implements ConfigurationProvider {
saveConfig(this.configFile, this.config); saveConfig(this.configFile, this.config);
} }
public void saveConfig(final File file) {
saveConfig(file, this.config);
}
@Override @Override
public void reloadConfig() { public void reloadConfig() {
this.configFile.getParentFile().mkdirs(); this.configFile.getParentFile().mkdirs();

View File

@ -166,11 +166,11 @@ cache-1_17-light: true
# #
# 1.19 chat type formats used for 1.19.1+ clients. # 1.19 chat type formats used for 1.19.1+ clients.
chat-types-1_19: chat-types-1_19:
"chat.type.text": "<%s> %s" chat.type.text: "<%s> %s"
"chat.type.announcement": "[%s] %s" chat.type.announcement: "[%s] %s"
"commands.message.display.incoming": "%s whispers to you: %s" commands.message.display.incoming: "%s whispers to you: %s"
"chat.type.team.text": "%s <%s> %s" chat.type.team.text: "%s <%s> %s"
"chat.type.emote": "* %s %s" chat.type.emote: "* %s %s"
# #
#----------------------------------------------------------# #----------------------------------------------------------#
# 1.9+ CLIENTS ON 1.8 SERVERS OPTIONS # # 1.9+ CLIENTS ON 1.8 SERVERS OPTIONS #