#293 Translatable help messages: basic structure

(work in progress)
- Create service that provides localized messages when available for HelpProvider
This commit is contained in:
ljacqu 2016-10-01 14:40:08 +02:00
parent 113a3f346c
commit f6a2b2b34b
7 changed files with 256 additions and 57 deletions

View File

@ -9,7 +9,7 @@ import java.util.List;
/**
* Helper class for displaying the syntax of a command properly to a user.
*/
class CommandSyntaxHelper {
final class CommandSyntaxHelper {
private CommandSyntaxHelper() {
}

View File

@ -0,0 +1,49 @@
package fr.xephi.authme.command.help;
/**
* Common, non-generic keys for messages used when showing command help.
* All keys are prefixed with {@code common}.
*/
public enum HelpMessageKey {
SHORT_DESCRIPTION("description.short", "Short description"),
DETAILED_DESCRIPTION("description.detailed", "Detailed description"),
USAGE("usage", "Usage"),
ARGUMENTS("arguments", "Arguments"),
OPTIONAL("optional", "(Optional)"),
HAS_PERMISSION("hasPermission", "You have permission"),
NO_PERMISSION("noPermission", "No permission"),
ALTERNATIVES("alternatives", "Alternatives"),
DEFAULT("default", "Default"),
RESULT("result", "Result"),
PERMISSIONS("permissions", "Permissions"),
COMMANDS("commands", "Commands");
private final String key;
private final String fallback;
HelpMessageKey(String key, String fallback) {
this.key = "common." + key;
this.fallback = fallback;
}
public String getKey() {
return key;
}
public String getFallback() {
return fallback;
}
}

View File

@ -0,0 +1,113 @@
package fr.xephi.authme.command.help;
import com.google.common.base.CaseFormat;
import fr.xephi.authme.command.CommandArgumentDescription;
import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.CommandUtils;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.permission.DefaultPermission;
import fr.xephi.authme.util.FileUtils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.inject.Inject;
import java.io.File;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Manages translatable help messages.
*/
public class HelpMessagesService implements Reloadable {
private static final String COMMAND_PREFIX = "command.";
private static final String DESCRIPTION_SUFFIX = ".description";
private static final String DETAILED_DESCRIPTION_SUFFIX = ".detailedDescription";
private static final String DEFAULT_PERMISSIONS_PATH = "common.defaultPermissions.";
private final File dataFolder;
// FIXME: Make configurable
private String file = "messages/help_en.yml";
private FileConfiguration fileConfiguration;
@Inject
HelpMessagesService(@DataFolder File dataFolder) {
File messagesFile = new File(dataFolder, "messages/help_en.yml");
if (!FileUtils.copyFileFromResource(messagesFile, file)) {
throw new IllegalStateException("Could not copy help message");
}
this.dataFolder = dataFolder;
fileConfiguration = YamlConfiguration.loadConfiguration(messagesFile);
}
/**
* Creates a copy of the supplied command description with localized messages where present.
*
* @param command the command to build a localized version of
* @return the localized description
*/
public CommandDescription buildLocalizedDescription(CommandDescription command) {
final String path = getCommandPath(command);
if (fileConfiguration.get(path) == null) {
// Messages file does not have a section for this command - return the provided command
return command;
}
CommandDescription.CommandBuilder builder = CommandDescription.builder()
.description(getText(path + DESCRIPTION_SUFFIX, command::getDescription))
.detailedDescription(getText(path + DETAILED_DESCRIPTION_SUFFIX, command::getDetailedDescription))
.executableCommand(command.getExecutableCommand())
.parent(command.getParent())
.labels(command.getLabels())
.permission(command.getPermission());
int i = 1;
for (CommandArgumentDescription argument : command.getArguments()) {
String argPath = path + ".arg" + i;
String label = getText(argPath + ".label", argument::getName);
String description = getText(argPath + ".description", argument::getDescription);
builder.withArgument(label, description, argument.isOptional());
++i;
}
return builder.build();
}
public String getMessage(HelpMessageKey key) {
String message = fileConfiguration.getString(key.getKey());
return message == null
? key.getFallback()
: message;
}
public String getMessage(DefaultPermission defaultPermission) {
// e.g. {default_permissions_path}.opOnly for DefaultPermission.OP_ONLY
String path = DEFAULT_PERMISSIONS_PATH +
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
String message = fileConfiguration.getString(path);
if (message != null) {
return message;
}
return defaultPermission.name(); // FIXME: Default message
}
@Override
public void reload() {
fileConfiguration = YamlConfiguration.loadConfiguration(new File(dataFolder, "messages/help_en.yml"));
}
private String getText(String path, Supplier<String> defaultTextGetter) {
String message = fileConfiguration.getString(path);
return message == null
? defaultTextGetter.get()
: message;
}
private static String getCommandPath(CommandDescription command) {
return COMMAND_PREFIX + CommandUtils.constructParentList(command)
.stream()
.map(cmd -> cmd.getLabels().get(0))
.collect(Collectors.joining("."));
}
}

View File

@ -19,6 +19,8 @@ import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import static fr.xephi.authme.command.help.HelpMessageKey.DETAILED_DESCRIPTION;
import static fr.xephi.authme.command.help.HelpMessageKey.SHORT_DESCRIPTION;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@ -45,11 +47,13 @@ public class HelpProvider implements SettingsDependent {
public static final int ALL_OPTIONS = ~HIDE_COMMAND;
private final PermissionsManager permissionsManager;
private final HelpMessagesService helpMessagesService;
private String helpHeader;
@Inject
HelpProvider(PermissionsManager permissionsManager, Settings settings) {
HelpProvider(PermissionsManager permissionsManager, HelpMessagesService helpMessagesService, Settings settings) {
this.permissionsManager = permissionsManager;
this.helpMessagesService = helpMessagesService;
reload(settings);
}
@ -61,7 +65,7 @@ public class HelpProvider implements SettingsDependent {
List<String> lines = new ArrayList<>();
lines.add(ChatColor.GOLD + "==========[ " + helpHeader + " HELP ]==========");
CommandDescription command = result.getCommandDescription();
CommandDescription command = helpMessagesService.buildLocalizedDescription(result.getCommandDescription());
List<String> labels = ImmutableList.copyOf(result.getLabels());
List<String> correctLabels = ImmutableList.copyOf(filterCorrectLabels(command, labels));
@ -75,7 +79,7 @@ public class HelpProvider implements SettingsDependent {
printArguments(command, lines);
}
if (hasFlag(SHOW_PERMISSIONS, options) && sender != null) {
printPermissions(command, sender, permissionsManager, lines);
printPermissions(command, sender, lines);
}
if (hasFlag(SHOW_ALTERNATIVES, options)) {
printAlternatives(command, correctLabels, lines);
@ -106,37 +110,40 @@ public class HelpProvider implements SettingsDependent {
helpHeader = settings.getProperty(PluginSettings.HELP_HEADER);
}
private static void printDetailedDescription(CommandDescription command, List<String> lines) {
lines.add(ChatColor.GOLD + "Short description: " + ChatColor.WHITE + command.getDescription());
lines.add(ChatColor.GOLD + "Detailed description:");
private void printDetailedDescription(CommandDescription command, List<String> lines) {
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(SHORT_DESCRIPTION) + ": "
+ ChatColor.WHITE + command.getDescription());
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(DETAILED_DESCRIPTION) + ":");
lines.add(ChatColor.WHITE + " " + command.getDetailedDescription());
}
private static void printArguments(CommandDescription command, List<String> lines) {
private void printArguments(CommandDescription command, List<String> lines) {
if (command.getArguments().isEmpty()) {
return;
}
lines.add(ChatColor.GOLD + "Arguments:");
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.ARGUMENTS) + ":");
StringBuilder argString = new StringBuilder();
String optionalText = " (" + helpMessagesService.getMessage(HelpMessageKey.OPTIONAL) + ")";
for (CommandArgumentDescription argument : command.getArguments()) {
argString.setLength(0);
argString.append(" ").append(ChatColor.YELLOW).append(ChatColor.ITALIC).append(argument.getName())
.append(": ").append(ChatColor.WHITE).append(argument.getDescription());
if (argument.isOptional()) {
argString.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(" (Optional)");
argString.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(optionalText);
}
lines.add(argString.toString());
}
}
private static void printAlternatives(CommandDescription command, List<String> correctLabels, List<String> lines) {
private void printAlternatives(CommandDescription command, List<String> correctLabels, List<String> lines) {
if (command.getLabels().size() <= 1 || correctLabels.size() <= 1) {
return;
}
lines.add(ChatColor.GOLD + "Alternatives:");
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.ALTERNATIVES) + ":");
// Get the label used
final String parentLabel = correctLabels.get(0);
final String childLabel = correctLabels.get(1);
@ -149,44 +156,53 @@ public class HelpProvider implements SettingsDependent {
}
}
private static void printPermissions(CommandDescription command, CommandSender sender,
PermissionsManager permissionsManager, List<String> lines) {
private void printPermissions(CommandDescription command, CommandSender sender, List<String> lines) {
PermissionNode permission = command.getPermission();
if (permission == null) {
return;
}
lines.add(ChatColor.GOLD + "Permissions:");
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.PERMISSIONS) + ":");
boolean hasPermission = permissionsManager.hasPermission(sender, permission);
final String nodePermsString = "" + ChatColor.GRAY + ChatColor.ITALIC
+ (hasPermission ? " (You have permission)" : " (No permission)");
lines.add(" " + ChatColor.YELLOW + ChatColor.ITALIC + permission.getNode() + nodePermsString);
lines.add(String.format(" " + ChatColor.YELLOW + ChatColor.ITALIC + "%s" + ChatColor.GRAY + " (%s)",
permission.getNode(), getLocalPermissionText(hasPermission)));
// Addendum to the line to specify whether the sender has permission or not when default is OP_ONLY
final DefaultPermission defaultPermission = permission.getDefaultPermission();
String addendum = "";
if (DefaultPermission.OP_ONLY.equals(defaultPermission)) {
addendum = defaultPermission.evaluate(sender)
? " (You have permission)"
: " (No permission)";
addendum = " (" + getLocalPermissionText(defaultPermission.evaluate(sender)) + ")";
}
lines.add(ChatColor.GOLD + "Default: " + ChatColor.GRAY + ChatColor.ITALIC
+ defaultPermission.getTitle() + addendum);
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.DEFAULT) + ": "
+ ChatColor.GRAY + ChatColor.ITALIC + helpMessagesService.getMessage(defaultPermission) + addendum);
// Evaluate if the sender has permission to the command
ChatColor permissionColor;
String permissionText;
if (permissionsManager.hasPermission(sender, command.getPermission())) {
lines.add(ChatColor.GOLD + " Result: " + ChatColor.GREEN + ChatColor.ITALIC + "You have permission");
permissionColor = ChatColor.GREEN;
permissionText = getLocalPermissionText(true);
} else {
lines.add(ChatColor.GOLD + " Result: " + ChatColor.DARK_RED + ChatColor.ITALIC + "No permission");
permissionColor = ChatColor.DARK_RED;
permissionText = getLocalPermissionText(false);
}
lines.add(String.format(ChatColor.GOLD + " %s: %s" + ChatColor.ITALIC + "%s",
helpMessagesService.getMessage(HelpMessageKey.RESULT), permissionColor, permissionText));
}
private static void printChildren(CommandDescription command, List<String> parentLabels, List<String> lines) {
private String getLocalPermissionText(boolean hasPermission) {
if (hasPermission) {
return helpMessagesService.getMessage(HelpMessageKey.HAS_PERMISSION);
}
return helpMessagesService.getMessage(HelpMessageKey.NO_PERMISSION);
}
private void printChildren(CommandDescription command, List<String> parentLabels, List<String> lines) {
if (command.getChildren().isEmpty()) {
return;
}
lines.add(ChatColor.GOLD + "Commands:");
lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpMessageKey.COMMANDS) + ":");
String parentCommandPath = CommandUtils.labelsToString(parentLabels);
for (CommandDescription child : command.getChildren()) {
lines.add(" /" + parentCommandPath + " " + child.getLabels().get(0)

View File

@ -8,7 +8,7 @@ import org.bukkit.permissions.ServerOperator;
public enum DefaultPermission {
/** No one has permission. */
NOT_ALLOWED("No permission") {
NOT_ALLOWED {
@Override
public boolean evaluate(ServerOperator sender) {
return false;
@ -16,7 +16,7 @@ public enum DefaultPermission {
},
/** Only players with OP status have permission. */
OP_ONLY("OP's only") {
OP_ONLY {
@Override
public boolean evaluate(ServerOperator sender) {
return sender != null && sender.isOp();
@ -24,24 +24,13 @@ public enum DefaultPermission {
},
/** Everyone is granted permission. */
ALLOWED("Everyone allowed") {
ALLOWED {
@Override
public boolean evaluate(ServerOperator sender) {
return true;
}
};
/** Textual representation of the default permission. */
private final String title;
/**
* Constructor.
* @param title The textual representation
*/
DefaultPermission(String title) {
this.title = title;
}
/**
* Evaluates whether permission is granted to the sender or not.
*
@ -50,13 +39,4 @@ public enum DefaultPermission {
*/
public abstract boolean evaluate(ServerOperator sender);
/**
* Return the textual representation.
*
* @return the textual representation
*/
public String getTitle() {
return title;
}
}

View File

@ -0,0 +1,30 @@
common:
description.short: 'Short description'
description.detailed: 'Detailed description'
usage: 'Usage'
arguments: 'Arguments'
permissions: 'Permissions'
optional: '(Optional)'
hasPermission: 'You have permission'
noPermission: 'No permission'
default: 'Default'
alternatives: 'Alternatives'
result: 'Result'
commands: 'Commands'
defaultPermissions:
notAllowed: 'No permission'
opOnly: 'OP''s only'
allowed: 'Everyone allowed'
command:
register:
description: 'Register with this command'
detailedDescription: 'Use /register <pw> <pw>'
authme.register:
description: 'Admin registration'
detailedDescription: 'Used by l33t adminz'
arg1:
label: 'n4me'
description: 'The name to save'
arg2:
label: 'passw0rd'
description: 'Password to use yo'

View File

@ -1,5 +1,8 @@
package fr.xephi.authme.command.help;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.FoundResultStatus;
@ -10,10 +13,12 @@ import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.util.Arrays;
import java.util.Collections;
@ -41,12 +46,21 @@ import static org.mockito.Mockito.verify;
/**
* Test for {@link HelpProvider}.
*/
@RunWith(DelayedInjectionRunner.class)
public class HelpProviderTest {
private static final String HELP_HEADER = "Help";
private static Set<CommandDescription> commands;
@InjectDelayed
private HelpProvider helpProvider;
@Mock
private PermissionsManager permissionsManager;
@Mock
private HelpMessagesService helpMessagesService;
@Mock
private Settings settings;
@Mock
private CommandSender sender;
@BeforeClass
@ -54,16 +68,13 @@ public class HelpProviderTest {
commands = TestCommandsUtil.generateCommands();
}
@Before
public void setUpHelpProvider() {
permissionsManager = mock(PermissionsManager.class);
Settings settings = mock(Settings.class);
@BeforeInjecting
public void setInitialSettings() {
given(settings.getProperty(PluginSettings.HELP_HEADER)).willReturn(HELP_HEADER);
helpProvider = new HelpProvider(permissionsManager, settings);
sender = mock(CommandSender.class);
}
@Test
@Ignore // FIXME: Fix test
public void shouldShowLongDescription() {
// given
CommandDescription command = getCommandWithLabel(commands, "authme", "login");