diff --git a/src/tools/messages/VerifyMessagesTask.java b/src/tools/messages/VerifyMessagesTask.java index e540554a..28b42b71 100644 --- a/src/tools/messages/VerifyMessagesTask.java +++ b/src/tools/messages/VerifyMessagesTask.java @@ -93,7 +93,7 @@ public final class VerifyMessagesTask implements ToolTask { } } - private static void verifyFileAndAddKeys(MessageFileVerifier verifier, FileConfiguration defaultMessages) { + public static void verifyFileAndAddKeys(MessageFileVerifier verifier, FileConfiguration defaultMessages) { Map missingKeys = verifier.getMissingKeys(); if (!missingKeys.isEmpty() || !verifier.getMissingTags().isEmpty()) { verifier.addMissingKeys(defaultMessages); diff --git a/src/tools/messages/translation/AuthMeYamlConfiguration.java b/src/tools/messages/translation/AuthMeYamlConfiguration.java new file mode 100644 index 00000000..f8724dff --- /dev/null +++ b/src/tools/messages/translation/AuthMeYamlConfiguration.java @@ -0,0 +1,58 @@ +package messages.translation; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.IOException; +import java.util.regex.Pattern; + +/** + * Extension of {@link YamlConfiguration} to customize the writing style. + */ +public class AuthMeYamlConfiguration extends YamlConfiguration { + + // Differences to YamlConfiguration: Texts are always in single quotes + // and line breaks are only applied after 200 chars + @Override + public String saveToString() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED); + options.setPrettyFlow(true); + options.setWidth(200); + Yaml yaml = new Yaml(options); + + String header = buildHeader(); + String dump = yaml.dump(getValues(false)); + + if (dump.equals(BLANK_CONFIG)) { + dump = ""; + } + // By setting the scalar style to SINGLE_QUOTED both keys and values will be enclosed in single quotes. + // We want all texts wrapped in single quotes, but not the keys. Seems like this is not possible in SnakeYAML + dump = Pattern.compile("^'([a-zA-Z0-9-_]+)': ", Pattern.MULTILINE) + .matcher(dump).replaceAll("$1: "); + + return header + dump; + } + + /** + * Behaves similarly to {@link YamlConfiguration#loadConfiguration(File)} but returns an object + * of this class instead. + * + * @param file the file to load + * @return the constructed AuthMeYamlConfiguration instance + */ + public static AuthMeYamlConfiguration loadConfiguration(File file) { + AuthMeYamlConfiguration config = new AuthMeYamlConfiguration(); + try { + config.load(file); + } catch (IOException | InvalidConfigurationException ex) { + throw new IllegalStateException(ex); + } + return config; + } +} diff --git a/src/tools/messages/translation/ImportMessagesTask.java b/src/tools/messages/translation/ImportMessagesTask.java index 4b75c9f2..36e8c29d 100644 --- a/src/tools/messages/translation/ImportMessagesTask.java +++ b/src/tools/messages/translation/ImportMessagesTask.java @@ -2,21 +2,27 @@ package messages.translation; import com.google.common.io.Resources; import com.google.gson.Gson; +import messages.MessageFileVerifier; +import messages.VerifyMessagesTask; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import org.yaml.snakeyaml.DumperOptions; +import utils.FileUtils; import utils.ToolTask; import utils.ToolsConstants; import java.io.File; import java.io.IOException; -import java.lang.reflect.Field; import java.net.URL; import java.nio.charset.Charset; import java.util.Scanner; +import java.util.regex.Pattern; /** - * Imports a message file from a JSON export. + * Imports a message file from a remote JSON export and validates the resulting file. + *

+ * Comments at the top of an existing file should remain after the import, but it is important + * to verify that no unwanted changes have been applied to the file. Note that YAML comments + * tend to disappear if there is no space between the # and the first character. */ public class ImportMessagesTask implements ToolTask { @@ -30,8 +36,9 @@ public class ImportMessagesTask implements ToolTask { @Override public void execute(Scanner scanner) { - System.out.println("Enter URL to export from"); + System.out.println("Enter URL to import from"); String url = scanner.nextLine(); + LanguageExport languageExport = getLanguageExportFromUrl(url); if (languageExport == null) { throw new IllegalStateException("An error occurred: constructed language export is null"); @@ -41,10 +48,10 @@ public class ImportMessagesTask implements ToolTask { System.out.println("Saved to messages file for code '" + languageExport.code + "'"); } - private LanguageExport getLanguageExportFromUrl(String url) { + private LanguageExport getLanguageExportFromUrl(String location) { try { - URL uri = new URL(url); - String json = Resources.toString(uri, Charset.forName("UTF-8")); + URL url = new URL(location); + String json = Resources.toString(url, Charset.forName("UTF-8")); return gson.fromJson(json, LanguageExport.class); } catch (IOException e) { throw new IllegalStateException(e); @@ -53,23 +60,40 @@ public class ImportMessagesTask implements ToolTask { private void mergeExportIntoFile(LanguageExport export) { String languageCode = export.code; - File file = new File(MESSAGES_FOLDER + "messages_" + languageCode + ".yml"); + String fileName = MESSAGES_FOLDER + "messages_" + languageCode + ".yml"; + File file = new File(fileName); if (!file.exists()) { throw new IllegalStateException("Messages file for language code " + languageCode + " does not exist"); } - FileConfiguration fileConfiguration = YamlConfiguration.loadConfiguration(file); + removeAllTodoComments(fileName); + FileConfiguration fileConfiguration = AuthMeYamlConfiguration.loadConfiguration(file); for (MessageExport messageExport : export.messages) { - fileConfiguration.set(messageExport.key, messageExport.translatedMessage); + if (!messageExport.translatedMessage.isEmpty()) { + fileConfiguration.set(messageExport.key, messageExport.translatedMessage); + } } try { - Field dumperOptionsField = YamlConfiguration.class.getDeclaredField("yamlOptions"); - dumperOptionsField.setAccessible(true); - DumperOptions options = (DumperOptions) dumperOptionsField.get(fileConfiguration); - options.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED); fileConfiguration.save(file); - } catch (IOException | NoSuchFieldException | IllegalAccessException e) { + } catch (IOException e) { throw new IllegalStateException(e); } + + MessageFileVerifier verifier = new MessageFileVerifier(fileName); + VerifyMessagesTask.verifyFileAndAddKeys(verifier, YamlConfiguration.loadConfiguration( + new File(MESSAGES_FOLDER + "messages_en.yml"))); + } + + /** + * Removes all to-do comments written by {@link VerifyMessagesTask}. This is helpful as the YamlConfiguration + * moves those comments otherwise upon saving. + * + * @param file The file whose to-do comments should be removed + */ + private static void removeAllTodoComments(String file) { + String contents = FileUtils.readFromFile(file); + String regex = "^# TODO .*$"; + contents = Pattern.compile(regex, Pattern.MULTILINE).matcher(contents).replaceAll(""); + FileUtils.writeToFile(file, contents); } }