From 8f6643207e7fc6ad5fb867c0803f5cc94aa7dd87 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 23 Oct 2016 16:25:34 +0200 Subject: [PATCH] #979 Create tool task to verify help translations --- .../HelpTranslationVerifier.java | 162 ++++++++++++++++++ .../VerifyHelpTranslations.java | 72 ++++++++ 2 files changed, 234 insertions(+) create mode 100644 src/test/java/tools/helptranslation/HelpTranslationVerifier.java create mode 100644 src/test/java/tools/helptranslation/VerifyHelpTranslations.java diff --git a/src/test/java/tools/helptranslation/HelpTranslationVerifier.java b/src/test/java/tools/helptranslation/HelpTranslationVerifier.java new file mode 100644 index 00000000..2ed130fa --- /dev/null +++ b/src/test/java/tools/helptranslation/HelpTranslationVerifier.java @@ -0,0 +1,162 @@ +package tools.helptranslation; + +import com.google.common.collect.Sets; +import de.bananaco.bpermissions.imp.YamlConfiguration; +import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandInitializer; +import fr.xephi.authme.command.CommandUtils; +import fr.xephi.authme.command.help.HelpMessage; +import fr.xephi.authme.command.help.HelpSection; +import org.bukkit.configuration.MemorySection; +import org.bukkit.configuration.file.FileConfiguration; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.google.common.collect.Lists.newArrayList; + +/** + * Verifies a help messages translation. + */ +public class HelpTranslationVerifier { + + private final FileConfiguration configuration; + + // missing and unknown HelpSection and HelpMessage entries + private final List missingSections = new ArrayList<>(); + private final List unknownSections = new ArrayList<>(); + // missing and unknown command entries + private final List missingCommands = new ArrayList<>(); + private final List unknownCommands = new ArrayList<>(); + + public HelpTranslationVerifier(File translation) { + this.configuration = YamlConfiguration.loadConfiguration(translation); + checkFile(); + } + + private void checkFile() { + checkHelpSections(); + checkCommands(); + } + + public List getMissingSections() { + return missingSections; + } + + public List getUnknownSections() { + return unknownSections; + } + + public List getMissingCommands() { + return missingCommands; + } + + public List getUnknownCommands() { + return unknownCommands; + } + + /** + * Verifies that the file has the expected entries for {@link HelpSection} and {@link HelpMessage}. + */ + private void checkHelpSections() { + Set knownSections = Arrays.stream(HelpSection.values()) + .map(HelpSection::getKey).collect(Collectors.toSet()); + knownSections.addAll(Arrays.stream(HelpMessage.values()).map(HelpMessage::getKey).collect(Collectors.toSet())); + knownSections.addAll(Arrays.asList("common.defaultPermissions.notAllowed", + "common.defaultPermissions.opOnly", "common.defaultPermissions.allowed")); + Set sectionKeys = getLeafKeys("section"); + sectionKeys.addAll(getLeafKeys("common")); + + if (sectionKeys.isEmpty()) { + missingSections.addAll(knownSections); + } else { + missingSections.addAll(Sets.difference(knownSections, sectionKeys)); + unknownSections.addAll(Sets.difference(sectionKeys, knownSections)); + } + } + + /** + * Verifies that the file has the expected entries for AuthMe commands. + */ + private void checkCommands() { + Set commandPaths = buildCommandPaths(); + Set existingKeys = getLeafKeys("commands"); + if (existingKeys.isEmpty()) { + missingCommands.addAll(commandPaths); + } else { + missingCommands.addAll(Sets.difference(commandPaths, existingKeys)); + unknownCommands.addAll(Sets.difference(existingKeys, commandPaths)); + } + } + + private static Set buildCommandPaths() { + Set commandPaths = new LinkedHashSet<>(); + for (CommandDescription command : new CommandInitializer().getCommands()) { + commandPaths.addAll(getYamlPaths(command)); + command.getChildren().forEach(child -> commandPaths.addAll(getYamlPaths(child))); + } + return commandPaths; + } + + private static List getYamlPaths(CommandDescription command) { + // e.g. commands.authme.register + String commandPath = "commands." + CommandUtils.constructParentList(command).stream() + .map(cmd -> cmd.getLabels().get(0)) + .collect(Collectors.joining(".")); + + // Entries each command can have + List paths = newArrayList(commandPath + ".description", commandPath + ".detailedDescription"); + + // Add argument entries that may exist + for (int argIndex = 1; argIndex <= command.getArguments().size(); ++argIndex) { + String argPath = String.format("%s.arg%d", commandPath, argIndex); + paths.add(argPath + ".label"); + paths.add(argPath + ".description"); + } + return paths; + } + + /** + * Returns the leaf keys of the section at the given path of the file configuration. + * + * @param path the path whose leaf keys should be retrieved + * @return leaf keys of the memory section, + * empty set if the configuration does not have a memory section at the given path + */ + private Set getLeafKeys(String path) { + if (!(configuration.get(path) instanceof MemorySection)) { + return Collections.emptySet(); + } + MemorySection memorySection = (MemorySection) configuration.get(path); + + // MemorySection#getKeys(true) returns all keys on all levels, e.g. if the configuration has + // 'commands.authme.register' then it also has 'commands.authme' and 'commands'. We can traverse each node and + // build its parents (e.g. for commands.authme.register.description: commands.authme.register, commands.authme, + // and commands, which we can remove from the collection since we know they are not a leaf. + Set leafKeys = memorySection.getKeys(true); + Set allKeys = new HashSet<>(leafKeys); + + for (String key : allKeys) { + List pathParts = Arrays.asList(key.split("\\.")); + + // We perform construction of parents & their removal in reverse order so we can build the lowest-level + // parent of a node first. As soon as the parent doesn't exist in the set already, we know we can continue + // with the next node since another node has already removed the concerned parents. + for (int i = pathParts.size() - 1; i > 0; --i) { + // e.g. for commands.authme.register -> i = {2, 1} => {commands.authme, commands} + String parentPath = String.join(".", pathParts.subList(0, i)); + if (!leafKeys.remove(parentPath)) { + break; + } + } + } + return leafKeys.stream().map(leaf -> path + "." + leaf).collect(Collectors.toSet()); + } +} diff --git a/src/test/java/tools/helptranslation/VerifyHelpTranslations.java b/src/test/java/tools/helptranslation/VerifyHelpTranslations.java new file mode 100644 index 00000000..f97447bf --- /dev/null +++ b/src/test/java/tools/helptranslation/VerifyHelpTranslations.java @@ -0,0 +1,72 @@ +package tools.helptranslation; + +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Verifies the help translations for validity and completeness. + */ +public class VerifyHelpTranslations implements ToolTask { + + private static final Pattern HELP_MESSAGE_PATTERN = Pattern.compile("help_[a-z]{2,7}\\.yml"); + private static final String FOLDER = ToolsConstants.MAIN_RESOURCES_ROOT + "messages/"; + + @Override + public String getTaskName() { + return "verifyHelpTranslations"; + } + + @Override + public void execute(Scanner scanner) { + System.out.println("Check specific language file?"); + System.out.println("Enter the language code for a specific file (e.g. 'it' for help_it.yml)"); + System.out.println("Empty line will check all files in the resources messages folder (default)"); + + String language = scanner.nextLine(); + if (language.isEmpty()) { + getHelpTranslations().forEach(this::processFile); + } else { + processFile(new File(FOLDER, "help_" + language + ".yml")); + } + } + + private void processFile(File file) { + System.out.println("Checking '" + file.getName() + "'"); + HelpTranslationVerifier verifier = new HelpTranslationVerifier(file); + + // Check and output errors + if (!verifier.getMissingSections().isEmpty()) { + System.out.println("Missing sections: " + String.join(", ", verifier.getMissingSections())); + } + if (!verifier.getUnknownSections().isEmpty()) { + System.out.println("Unknown sections: " + String.join(", ", verifier.getUnknownSections())); + } + if (!verifier.getMissingCommands().isEmpty()) { + System.out.println("Missing command entries: " + String.join(", ", verifier.getMissingCommands())); + } + if (!verifier.getUnknownCommands().isEmpty()) { + System.out.println("Unknown command entries: " + String.join(", ", verifier.getUnknownCommands())); + } + } + + private static List getHelpTranslations() { + File[] files = new File(FOLDER).listFiles(); + if (files == null) { + throw new IllegalStateException("Could not get files from '" + FOLDER + "'"); + } + List helpFiles = Arrays.stream(files) + .filter(file -> HELP_MESSAGE_PATTERN.matcher(file.getName()).matches()) + .collect(Collectors.toList()); + if (helpFiles.isEmpty()) { + throw new IllegalStateException("Could not get any matching files!"); + } + return helpFiles; + } +}