From f9bbb5b1a363b3589cad7dc3ae400e599302b5d0 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Mon, 1 Aug 2022 11:34:21 +0200 Subject: [PATCH] Fixup config comment saving Fixes #3052, #2536 --- .../viaversion/util/CommentStore.java | 230 +++++++++++------- .../viaversion/viaversion/util/Config.java | 11 +- .../resources/assets/viaversion/config.yml | 10 +- 3 files changed, 157 insertions(+), 94 deletions(-) diff --git a/common/src/main/java/com/viaversion/viaversion/util/CommentStore.java b/common/src/main/java/com/viaversion/viaversion/util/CommentStore.java index c93aac670..926160bfd 100644 --- a/common/src/main/java/com/viaversion/viaversion/util/CommentStore.java +++ b/common/src/main/java/com/viaversion/viaversion/util/CommentStore.java @@ -18,8 +18,6 @@ package com.viaversion.viaversion.util; 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.Files; @@ -28,19 +26,23 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; public class CommentStore { - private final Map> headers = Maps.newConcurrentMap(); - private final char pathSeperator; + private final Map> headers = new HashMap<>(); + private final String pathSeparator; + private final String pathSeparatorQuoted; private final int indents; - private List mainHeader = Lists.newArrayList(); + private List mainHeader = new ArrayList<>(); - public CommentStore(char pathSeperator, int indents) { - this.pathSeperator = pathSeperator; + public CommentStore(final char pathSeparator, final int indents) { + this.pathSeparator = Character.toString(pathSeparator); + this.pathSeparatorQuoted = Pattern.quote(this.pathSeparator); this.indents = indents; } @@ -49,7 +51,7 @@ public class CommentStore { * * @param header header */ - public void mainHeader(String... header) { + public void mainHeader(final String... header) { mainHeader = Arrays.asList(header); } @@ -68,8 +70,7 @@ public class CommentStore { * @param key of option (or section) * @param header of option (or section) */ - public void header(String key, String... header) { -// String value = Joiner.on('\n').join(header); + public void header(final String key, final String... header) { headers.put(key, Arrays.asList(header)); } @@ -79,126 +80,181 @@ public class CommentStore { * @param key of option (or section) * @return Header */ - public List header(String key) { + public List header(final String key) { return headers.get(key); } - public void storeComments(InputStream inputStream) throws IOException { - InputStreamReader reader = new InputStreamReader(inputStream); - String contents; - try { - contents = CharStreams.toString(reader); - } finally { - reader.close(); + public void storeComments(final InputStream inputStream) throws IOException { + final String data; + try (final InputStreamReader reader = new InputStreamReader(inputStream)) { + data = CharStreams.toString(reader); } - StringBuilder memoryData = new StringBuilder(); - // Parse headers - final String pathSeparator = Character.toString(this.pathSeperator); + + final List currentComments = new ArrayList<>(); + boolean header = true; + boolean multiLineValue = false; int currentIndents = 0; String key = ""; - List headers = Lists.newArrayList(); - for (String line : contents.split("\n")) { - if (line.isEmpty()) continue; // Skip empty lines - int indent = getSuccessiveCharCount(line, ' '); - String subline = indent > 0 ? line.substring(indent) : line; - 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); + for (final String line : data.split("\n")) { + final String s = line.trim(); + // It's a comment! + if (s.startsWith("#")) { + currentComments.add(s); continue; } - int indents = indent / this.indents; - if (indents <= currentIndents) { - // Remove last section of key - String[] array = key.split(Pattern.quote(pathSeparator)); - int backspace = currentIndents - indents + 1; - key = join(array, this.pathSeperator, 0, array.length - backspace); + // Header is over - save it! + if (header) { + if (!currentComments.isEmpty()) { + currentComments.add(""); + mainHeader.addAll(currentComments); + currentComments.clear(); + } + header = false; } - // Add new section to key - String separator = key.length() > 0 ? pathSeparator : ""; - String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; - key += separator + lineKey.substring(indent); + // 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 / 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; - memoryData.append(line).append('\n'); - if (!headers.isEmpty()) { - this.headers.put(key, headers); - headers = Lists.newArrayList(); + if (!currentComments.isEmpty()) { + headers.put(key, new ArrayList<>(currentComments)); + currentComments.clear(); } } } - public void writeComments(String yaml, File output) throws IOException { - // Custom save - final int indentLength = this.indents; - final String pathSeparator = Character.toString(this.pathSeperator); - 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'); + public void writeComments(final String rawYaml, final File output) throws IOException { + final StringBuilder fileData = new StringBuilder(); + for (final String mainHeaderLine : mainHeader) { + fileData.append(mainHeaderLine).append('\n'); } - for (String line : yaml.split("\n")) { - if (line.isEmpty() || line.trim().charAt(0) == '-') { + // Remove last new line + 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'); continue; } - int indent = getSuccessiveCharCount(line, ' '); - int indents = indent / indentLength; - String indentText = indent > 0 ? line.substring(0, indent) : ""; - if (indents <= currentIndents) { - // Remove last section of key - String[] array = key.split(Pattern.quote(pathSeparator)); - int backspace = currentIndents - indents + 1; - key = join(array, this.pathSeperator, 0, array.length - backspace); + 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 += this.pathSeparator; + } + key += newKey; + + // Add comments + final List 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 - String separator = !key.isEmpty() ? pathSeparator : ""; - String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line; - key += separator + lineKey.substring(indent); - - currentIndents = indents; - - List header = headers.get(key); - String headerText = header != null ? addHeaderTags(header, indentText) : ""; - fileData.append(headerText).append(line).append('\n'); + currentKeyIndents = indents; + fileData.append(line).append('\n'); } // Write data to file Files.write(fileData.toString(), output, StandardCharsets.UTF_8); } - private String addHeaderTags(List header, String indent) { - StringBuilder builder = new StringBuilder(); - for (String line : header) { + private String addHeaderTags(final List header, final String indent) { + final StringBuilder builder = new StringBuilder(); + for (final String line : header) { builder.append(indent).append("# ").append(line).append('\n'); } return builder.toString(); } - private String join(String[] array, char joinChar, int start, int length) { - String[] copy = new String[length - start]; + private String join(final String[] array, final char joinChar, final int start, final int length) { + final String[] copy = new String[length - start]; System.arraycopy(array, start, copy, 0, length - start); 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; for (int i = 0; i < text.length(); i++) { - if (text.charAt(i) == key) { + if (text.charAt(i) == ' ') { count += 1; } else { break; diff --git a/common/src/main/java/com/viaversion/viaversion/util/Config.java b/common/src/main/java/com/viaversion/viaversion/util/Config.java index 9838b29e7..1b13cd0f6 100644 --- a/common/src/main/java/com/viaversion/viaversion/util/Config.java +++ b/common/src/main/java/com/viaversion/viaversion/util/Config.java @@ -62,9 +62,12 @@ public abstract class Config implements ConfigurationProvider { return getClass().getClassLoader().getResource("assets/viaversion/config.yml"); } - public synchronized Map loadConfig(File location) { + public Map loadConfig(File location) { + return loadConfig(location, getDefaultConfigURL()); + } + + public synchronized Map loadConfig(File location, URL jarConfigFile) { List unsupported = getUnsupportedOptions(); - URL jarConfigFile = getDefaultConfigURL(); try { commentStore.storeComments(jarConfigFile.openStream()); for (String option : unsupported) { @@ -137,6 +140,10 @@ public abstract class Config implements ConfigurationProvider { saveConfig(this.configFile, this.config); } + public void saveConfig(final File file) { + saveConfig(file, this.config); + } + @Override public void reloadConfig() { this.configFile.getParentFile().mkdirs(); diff --git a/common/src/main/resources/assets/viaversion/config.yml b/common/src/main/resources/assets/viaversion/config.yml index bc992dd42..b74390628 100644 --- a/common/src/main/resources/assets/viaversion/config.yml +++ b/common/src/main/resources/assets/viaversion/config.yml @@ -166,11 +166,11 @@ cache-1_17-light: true # # 1.19 chat type formats used for 1.19.1+ clients. chat-types-1_19: - "chat.type.text": "<%s> %s" - "chat.type.announcement": "[%s] %s" - "commands.message.display.incoming": "%s whispers to you: %s" - "chat.type.team.text": "%s <%s> %s" - "chat.type.emote": "* %s %s" + chat.type.text: "<%s> %s" + chat.type.announcement: "[%s] %s" + commands.message.display.incoming: "%s whispers to you: %s" + chat.type.team.text: "%s <%s> %s" + chat.type.emote: "* %s %s" # #----------------------------------------------------------# # 1.9+ CLIENTS ON 1.8 SERVERS OPTIONS #