#979 Create tool task to verify help translations

This commit is contained in:
ljacqu 2016-10-23 16:25:34 +02:00
parent dd9ac75f3a
commit 8f6643207e
2 changed files with 234 additions and 0 deletions

View File

@ -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<String> missingSections = new ArrayList<>();
private final List<String> unknownSections = new ArrayList<>();
// missing and unknown command entries
private final List<String> missingCommands = new ArrayList<>();
private final List<String> unknownCommands = new ArrayList<>();
public HelpTranslationVerifier(File translation) {
this.configuration = YamlConfiguration.loadConfiguration(translation);
checkFile();
}
private void checkFile() {
checkHelpSections();
checkCommands();
}
public List<String> getMissingSections() {
return missingSections;
}
public List<String> getUnknownSections() {
return unknownSections;
}
public List<String> getMissingCommands() {
return missingCommands;
}
public List<String> getUnknownCommands() {
return unknownCommands;
}
/**
* Verifies that the file has the expected entries for {@link HelpSection} and {@link HelpMessage}.
*/
private void checkHelpSections() {
Set<String> 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<String> 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<String> commandPaths = buildCommandPaths();
Set<String> 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<String> buildCommandPaths() {
Set<String> 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<String> 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<String> 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<String> 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<String> leafKeys = memorySection.getKeys(true);
Set<String> allKeys = new HashSet<>(leafKeys);
for (String key : allKeys) {
List<String> 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());
}
}

View File

@ -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<File> getHelpTranslations() {
File[] files = new File(FOLDER).listFiles();
if (files == null) {
throw new IllegalStateException("Could not get files from '" + FOLDER + "'");
}
List<File> 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;
}
}