Merge branch 'master' of https://github.com/AuthMe-Team/AuthMeReloaded into antibot-improvement

Conflicts:
	src/main/java/fr/xephi/authme/service/AntiBotService.java
This commit is contained in:
ljacqu
2016-10-30 08:56:16 +01:00
73 changed files with 2075 additions and 1163 deletions
@@ -18,6 +18,7 @@ import java.util.regex.Pattern;
import static fr.xephi.authme.permission.DefaultPermission.OP_ONLY;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@@ -32,7 +33,7 @@ public class CommandInitializerTest {
*/
private static int MAX_ALLOWED_DEPTH = 1;
private static Set<CommandDescription> commands;
private static Collection<CommandDescription> commands;
@BeforeClass
public static void initializeCommandCollection() {
@@ -46,7 +47,7 @@ public class CommandInitializerTest {
// It obviously doesn't make sense to test much of the concrete data
// that is being initialized; we just want to guarantee with this test
// that data is indeed being initialized and we take a few "probes"
assertThat(commands.size(), equalTo(8));
assertThat(commands, hasSize(8));
assertThat(commandsIncludeLabel(commands, "authme"), equalTo(true));
assertThat(commandsIncludeLabel(commands, "register"), equalTo(true));
assertThat(commandsIncludeLabel(commands, "help"), equalTo(false));
@@ -39,7 +39,7 @@ import static org.mockito.Mockito.mock;
@RunWith(DelayedInjectionRunner.class)
public class CommandMapperTest {
private static Set<CommandDescription> commands;
private static List<CommandDescription> commands;
@InjectDelayed
private CommandMapper mapper;
@@ -1,8 +1,15 @@
package fr.xephi.authme.command;
import fr.xephi.authme.TestHelper;
import org.bukkit.ChatColor;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@@ -11,6 +18,13 @@ import static org.junit.Assert.assertThat;
*/
public class CommandUtilsTest {
private static Collection<CommandDescription> commands;
@BeforeClass
public static void setUpTestCommands() {
commands = TestCommandsUtil.generateCommands();
}
@Test
public void shouldReturnCommandPath() {
// given
@@ -19,14 +33,14 @@ public class CommandUtilsTest {
.description("Base")
.detailedDescription("Test base command.")
.executableCommand(ExecutableCommand.class)
.build();
.register();
CommandDescription command = CommandDescription.builder()
.parent(base)
.labels("help", "h", "?")
.description("Child")
.detailedDescription("Test child command.")
.executableCommand(ExecutableCommand.class)
.build();
.register();
// when
String commandPath = CommandUtils.constructCommandPath(command);
@@ -42,7 +56,7 @@ public class CommandUtilsTest {
@Test
public void shouldComputeMinAndMaxOnEmptyCommand() {
// given
CommandDescription command = getBuilderForArgsTest().build();
CommandDescription command = getBuilderForArgsTest().register();
// when / then
checkArgumentCount(command, 0, 0);
@@ -54,7 +68,7 @@ public class CommandUtilsTest {
CommandDescription command = getBuilderForArgsTest()
.withArgument("Test", "Arg description", false)
.withArgument("Test22", "Arg description 2", false)
.build();
.register();
// when / then
checkArgumentCount(command, 2, 2);
@@ -67,7 +81,7 @@ public class CommandUtilsTest {
.withArgument("arg1", "Arg description", false)
.withArgument("arg2", "Arg description 2", true)
.withArgument("arg3", "Arg description 3", true)
.build();
.register();
// when / then
checkArgumentCount(command, 1, 3);
@@ -79,6 +93,46 @@ public class CommandUtilsTest {
TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandUtils.class);
}
@Test
public void shouldFormatSimpleArgument() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme");
List<String> labels = Collections.singletonList("authme");
// when
String result = CommandUtils.buildSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW));
}
@Test
public void shouldFormatCommandWithMultipleArguments() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme", "register");
List<String> labels = Arrays.asList("authme", "reg");
// when
String result = CommandUtils.buildSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW + " reg <password> <confirmation>"));
}
@Test
public void shouldFormatCommandWithOptionalArgument() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "email");
List<String> labels = Collections.singletonList("email");
// when
String result = CommandUtils.buildSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/email" + ChatColor.YELLOW + " [player]"));
}
private static void checkArgumentCount(CommandDescription command, int expectedMin, int expectedMax) {
assertThat(CommandUtils.getMinNumberOfArguments(command), equalTo(expectedMin));
@@ -1,5 +1,6 @@
package fr.xephi.authme.command;
import com.google.common.collect.ImmutableList;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PermissionNode;
@@ -8,9 +9,7 @@ import org.bukkit.command.CommandSender;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@@ -27,7 +26,7 @@ public final class TestCommandsUtil {
*
* @return The generated commands
*/
public static Set<CommandDescription> generateCommands() {
public static List<CommandDescription> generateCommands() {
// Register /authme
CommandDescription authMeBase = createCommand(null, null, singletonList("authme"), ExecutableCommand.class);
// Register /authme login <password>
@@ -42,13 +41,13 @@ public final class TestCommandsUtil {
newArgument("player", true));
// Register /email helptest -- use only to test for help command arguments special case
CommandDescription.builder().parent(emailBase).labels("helptest").executableCommand(HelpCommand.class)
.description("test").detailedDescription("Test.").withArgument("Query", "", false).build();
.description("test").detailedDescription("Test.").withArgument("Query", "", false).register();
// Register /unregister <player>, alias: /unreg
CommandDescription unregisterBase = createCommand(AdminPermission.UNREGISTER, null,
asList("unregister", "unreg"), TestUnregisterCommand.class, newArgument("player", false));
return newHashSet(authMeBase, emailBase, unregisterBase);
return ImmutableList.of(authMeBase, emailBase, unregisterBase);
}
/**
@@ -101,7 +100,7 @@ public final class TestCommandsUtil {
}
}
return command.build();
return command.register();
}
/** Shortcut command to initialize a new argument description. */
@@ -20,6 +20,10 @@ import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS;
import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.SUCCESS;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_ALTERNATIVES;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_CHILDREN;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_COMMAND;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_DESCRIPTION;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.containsString;
@@ -108,7 +112,7 @@ public class HelpCommandTest {
CommandDescription commandDescription = mock(CommandDescription.class);
given(commandDescription.getLabelCount()).willReturn(1);
FoundCommandResult foundCommandResult = new FoundCommandResult(commandDescription, singletonList("authme"),
Collections.<String>emptyList(), 0.0, SUCCESS);
Collections.emptyList(), 0.0, SUCCESS);
given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult);
// when
@@ -116,7 +120,8 @@ public class HelpCommandTest {
// then
verify(sender, never()).sendMessage(anyString());
verify(helpProvider).outputHelp(sender, foundCommandResult, HelpProvider.SHOW_CHILDREN);
verify(helpProvider).outputHelp(sender, foundCommandResult,
SHOW_DESCRIPTION | SHOW_COMMAND | SHOW_CHILDREN | SHOW_ALTERNATIVES);
}
@Test
@@ -126,7 +131,7 @@ public class HelpCommandTest {
CommandDescription commandDescription = mock(CommandDescription.class);
given(commandDescription.getLabelCount()).willReturn(2);
FoundCommandResult foundCommandResult = new FoundCommandResult(commandDescription, asList("authme", "getpos"),
Collections.<String>emptyList(), 0.0, INCORRECT_ARGUMENTS);
Collections.emptyList(), 0.0, INCORRECT_ARGUMENTS);
given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult);
// when
@@ -1,76 +0,0 @@
package fr.xephi.authme.command.help;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.TestCommandsUtil;
import org.bukkit.ChatColor;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Test for {@link CommandSyntaxHelper}.
*/
public class CommandSyntaxHelperTest {
private static Set<CommandDescription> commands;
@BeforeClass
public static void setUpTestCommands() {
commands = TestCommandsUtil.generateCommands();
}
@Test
public void shouldFormatSimpleArgument() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme");
List<String> labels = Collections.singletonList("authme");
// when
String result = CommandSyntaxHelper.getSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW));
}
@Test
public void shouldFormatCommandWithMultipleArguments() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme", "register");
List<String> labels = Arrays.asList("authme", "reg");
// when
String result = CommandSyntaxHelper.getSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW + " reg <password> <confirmation>"));
}
@Test
public void shouldFormatCommandWithOptionalArgument() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "email");
List<String> labels = Collections.singletonList("email");
// when
String result = CommandSyntaxHelper.getSyntax(command, labels);
// then
assertThat(result, equalTo(ChatColor.WHITE + "/email" + ChatColor.YELLOW + " [player]"));
}
@Test
public void shouldHaveHiddenConstructor() {
// given / when / then
TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandSyntaxHelper.class);
}
}
@@ -9,8 +9,8 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
@@ -78,7 +78,7 @@ public class HelpMessagesConsistencyTest {
* @return the CommandDescription object for the {@code /authme register} command.
*/
private static CommandDescription getAuthMeRegisterDescription() {
Set<CommandDescription> commands = new CommandInitializer().getCommands();
Collection<CommandDescription> commands = new CommandInitializer().getCommands();
List<CommandDescription> children = commands.stream()
.filter(command -> command.getLabels().contains("authme"))
@@ -12,10 +12,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.Set;
import java.util.Collection;
import java.util.function.Function;
import static fr.xephi.authme.TestHelper.getJarFile;
import static fr.xephi.authme.command.TestCommandsUtil.getCommandWithLabel;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.sameInstance;
@@ -30,7 +31,7 @@ import static org.mockito.Matchers.any;
public class HelpMessagesServiceTest {
private static final String TEST_FILE = "/fr/xephi/authme/command/help/help_test.yml";
private static final Set<CommandDescription> COMMANDS = TestCommandsUtil.generateCommands();
private static final Collection<CommandDescription> COMMANDS = TestCommandsUtil.generateCommands();
@InjectDelayed
private HelpMessagesService helpMessagesService;
@@ -48,7 +49,7 @@ public class HelpMessagesServiceTest {
@Test
public void shouldReturnLocalizedCommand() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(COMMANDS, "authme", "register");
CommandDescription command = getCommandWithLabel(COMMANDS, "authme", "register");
// when
CommandDescription localCommand = helpMessagesService.buildLocalizedDescription(command);
@@ -68,7 +69,7 @@ public class HelpMessagesServiceTest {
@Test
public void shouldReturnLocalizedCommandWithDefaults() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(COMMANDS, "authme", "login");
CommandDescription command = getCommandWithLabel(COMMANDS, "authme", "login");
// when
CommandDescription localCommand = helpMessagesService.buildLocalizedDescription(command);
@@ -84,7 +85,7 @@ public class HelpMessagesServiceTest {
@Test
public void shouldReturnSameCommandForNoLocalization() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(COMMANDS, "email");
CommandDescription command = getCommandWithLabel(COMMANDS, "email");
// when
CommandDescription localCommand = helpMessagesService.buildLocalizedDescription(command);
@@ -96,7 +97,7 @@ public class HelpMessagesServiceTest {
@Test
public void shouldKeepChildrenInLocalCommand() {
// given
CommandDescription command = TestCommandsUtil.getCommandWithLabel(COMMANDS, "authme");
CommandDescription command = getCommandWithLabel(COMMANDS, "authme");
// when
CommandDescription localCommand = helpMessagesService.buildLocalizedDescription(command);
@@ -114,4 +115,28 @@ public class HelpMessagesServiceTest {
assertThat(helpMessagesService.getMessage(HelpMessage.RESULT), equalTo("res."));
assertThat(helpMessagesService.getMessage(HelpSection.ARGUMENTS), equalTo("arg."));
}
@Test
public void shouldGetLocalCommandDescription() {
// given
CommandDescription command = getCommandWithLabel(COMMANDS, "authme", "register");
// when
String description = helpMessagesService.getDescription(command);
// then
assertThat(description, equalTo("Registration"));
}
@Test
public void shouldFallbackToDescriptionOnCommandObject() {
// given
CommandDescription command = getCommandWithLabel(COMMANDS, "unregister");
// when
String description = helpMessagesService.getDescription(command);
// then
assertThat(description, equalTo(command.getDescription()));
}
}
@@ -20,9 +20,9 @@ import org.mockito.Mock;
import org.mockito.internal.stubbing.answers.ReturnsArgumentAt;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static fr.xephi.authme.command.TestCommandsUtil.getCommandWithLabel;
@@ -53,7 +53,7 @@ import static org.mockito.Mockito.verify;
@RunWith(DelayedInjectionRunner.class)
public class HelpProviderTest {
private static Set<CommandDescription> commands;
private static Collection<CommandDescription> commands;
@InjectDelayed
private HelpProvider helpProvider;
@@ -251,6 +251,10 @@ public class HelpProviderTest {
// given
CommandDescription command = getCommandWithLabel(commands, "authme");
FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme"));
given(helpMessagesService.getDescription(getCommandWithLabel(commands, "authme", "login")))
.willReturn("Command for login [localized]");
given(helpMessagesService.getDescription(getCommandWithLabel(commands, "authme", "register")))
.willReturn("Registration command [localized]");
// when
helpProvider.outputHelp(sender, result, SHOW_CHILDREN);
@@ -258,9 +262,9 @@ public class HelpProviderTest {
// then
List<String> lines = getLines(sender);
assertThat(lines, hasSize(4));
assertThat(lines.get(1), containsString("Children:"));
assertThat(lines.get(2), containsString("/authme login: login cmd"));
assertThat(lines.get(3), containsString("/authme register: register cmd"));
assertThat(lines.get(1), equalTo("Children:"));
assertThat(lines.get(2), equalTo(" /authme login: Command for login [localized]"));
assertThat(lines.get(3), equalTo(" /authme register: Registration command [localized]"));
}
@Test
@@ -395,6 +399,24 @@ public class HelpProviderTest {
assertThat(lines.get(0), equalTo("Command: /authme register <password> <confirmation>"));
}
@Test
public void shouldShowAlternativesForRootCommand() {
// given
CommandDescription command = getCommandWithLabel(commands, "unregister");
FoundCommandResult result = newFoundResult(command, Collections.singletonList("unreg"));
// when
helpProvider.outputHelp(sender, result, SHOW_COMMAND | SHOW_ALTERNATIVES);
// then
List<String> lines = getLines(sender);
assertThat(lines, hasSize(4));
assertThat(lines.get(0), equalTo("Header"));
assertThat(lines.get(1), equalTo("Command: /unreg <player>"));
assertThat(lines.get(2), equalTo("Alternatives:"));
assertThat(lines.get(3), equalTo(" /unregister <player>"));
}
/**
* Generate an instance of {@link FoundCommandResult} with the given command and labels. All other fields aren't
* retrieved by {@link HelpProvider} and so are initialized to default values for the tests.
@@ -1,75 +0,0 @@
package fr.xephi.authme.message;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static org.junit.Assert.fail;
/**
* Tests that all YML message files can be loaded.
*/
public class MessagesFileYamlCheckerTest {
/** Path in the resources folder where the message files are located. */
private static final String MESSAGES_FOLDER = "/messages/";
/** Pattern of the message file names. */
private static final Pattern MESSAGE_FILE_PATTERN = Pattern.compile("messages_\\w+\\.yml");
/** Message key that is present in all files. Used to make sure that text is returned. */
private static final MessageKey MESSAGE_KEY = MessageKey.LOGIN_MESSAGE;
@Test
public void shouldAllBeValidYaml() {
// given
List<File> messageFiles = getMessageFiles();
// when
List<String> errors = new ArrayList<>();
for (File file : messageFiles) {
String error = null;
try {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
if (StringUtils.isEmpty(configuration.getString(MESSAGE_KEY.getKey()))) {
error = "Message for '" + MESSAGE_KEY + "' is empty";
}
} catch (Exception e) {
error = "Could not load file: " + StringUtils.formatException(e);
}
if (!StringUtils.isEmpty(error)) {
errors.add(file.getName() + ": " + error);
}
}
// then
if (!errors.isEmpty()) {
fail("Errors during verification of message files:\n-" + String.join("\n-", errors));
}
}
private List<File> getMessageFiles() {
File folder = TestHelper.getJarFile(MESSAGES_FOLDER);
File[] files = folder.listFiles();
if (files == null) {
throw new IllegalStateException("Could not read folder '" + folder.getName() + "'");
}
List<File> messageFiles = new ArrayList<>();
for (File file : files) {
if (MESSAGE_FILE_PATTERN.matcher(file.getName()).matches()) {
messageFiles.add(file);
}
}
if (messageFiles.isEmpty()) {
throw new IllegalStateException("Error getting message files: list of files is empty");
}
return messageFiles;
}
}
@@ -0,0 +1,98 @@
package fr.xephi.authme.message;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.command.help.HelpSection;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import static org.junit.Assert.fail;
/**
* Tests that all YML text files can be loaded.
*/
public class YamlTextFileCheckerTest {
/** Path in the resources folder where the message files are located. */
private static final String MESSAGES_FOLDER = "/messages/";
/** Contains all files of the MESSAGES_FOLDER. */
private static List<File> messageFiles;
@BeforeClass
public static void loadMessagesFiles() {
File folder = TestHelper.getJarFile(MESSAGES_FOLDER);
File[] files = folder.listFiles();
if (files == null || files.length == 0) {
throw new IllegalStateException("Could not read folder '" + folder.getName() + "'");
}
messageFiles = Arrays.asList(files);
}
@Test
public void testAllMessagesYmlFiles() {
checkFiles(
Pattern.compile("messages_\\w+\\.yml"),
MessageKey.LOGIN_MESSAGE.getKey());
}
@Test
public void testAllHelpYmlFiles() {
checkFiles(
Pattern.compile("help_\\w+\\.yml"),
HelpSection.ALTERNATIVES.getKey());
}
/**
* Checks all files in the messages folder that match the given pattern.
*
* @param pattern the pattern the file name needs to match
* @param mandatoryKey key present in all matched files
*/
private void checkFiles(Pattern pattern, String mandatoryKey) {
List<String> errors = new ArrayList<>();
boolean hasMatch = false;
for (File file : messageFiles) {
if (pattern.matcher(file.getName()).matches()) {
checkFile(file, mandatoryKey, errors);
hasMatch = true;
}
}
if (!errors.isEmpty()) {
fail("Errors while checking files matching '" + pattern + "':\n-" + String.join("\n-", errors));
} else if (!hasMatch) {
fail("Could not find any files satisfying pattern '" + pattern + "'");
}
}
/**
* Checks that the provided YAML file can be loaded and that it contains a non-empty text
* for the provided mandatory key.
*
* @param file the file to check
* @param mandatoryKey the key for which text must be present
* @param errors collection of errors to add to if the verification fails
*/
private void checkFile(File file, String mandatoryKey, List<String> errors) {
String error = null;
try {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
if (StringUtils.isEmpty(configuration.getString(mandatoryKey))) {
error = "Message for '" + mandatoryKey + "' is empty";
}
} catch (Exception e) {
error = "Could not load file: " + StringUtils.formatException(e);
}
if (!StringUtils.isEmpty(error)) {
errors.add(file.getName() + ": " + error);
}
}
}
@@ -26,7 +26,7 @@ public class PermissionConsistencyTest {
/** All classes defining permission nodes. */
private static final Set<Class<? extends PermissionNode>> PERMISSION_CLASSES = ImmutableSet
.<Class<? extends PermissionNode>>of(PlayerPermission.class, AdminPermission.class, PlayerStatePermission.class);
.of(PlayerPermission.class, AdminPermission.class, PlayerStatePermission.class);
/** Wildcard permissions (present in plugin.yml but not in the codebase). */
private static final Set<String> PLUGIN_YML_PERMISSIONS_WILDCARDS =
@@ -51,6 +51,7 @@ public class AntiBotServiceTest {
@BeforeInjecting
public void initSettings() {
given(settings.getProperty(ProtectionSettings.ANTIBOT_DURATION)).willReturn(10);
given(settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL)).willReturn(5);
given(settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY)).willReturn(5);
given(settings.getProperty(ProtectionSettings.ENABLE_ANTIBOT)).willReturn(true);
given(settings.getProperty(ProtectionSettings.ANTIBOT_DELAY)).willReturn(8);
@@ -1,105 +0,0 @@
package fr.xephi.authme.settings;
import com.github.authme.configme.knownproperties.ConfigurationData;
import com.github.authme.configme.migration.MigrationService;
import com.github.authme.configme.migration.PlainMigrationService;
import com.github.authme.configme.properties.Property;
import com.github.authme.configme.resource.PropertyResource;
import com.github.authme.configme.resource.YamlFileResource;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Test for {@link Settings} and the project's config.yml,
* verifying that no settings are missing from the file.
*/
public class ConfigFileConsistencyTest {
/** The file name of the project's sample config file. */
private static final String CONFIG_FILE = "/config.yml";
@Test
public void shouldHaveAllConfigs() throws IOException {
// given
File configFile = TestHelper.getJarFile(CONFIG_FILE);
PropertyResource resource = new YamlFileResource(configFile);
MigrationService migration = new PlainMigrationService();
// when
boolean result = migration.checkAndMigrate(
resource, AuthMeSettingsRetriever.buildConfigurationData().getProperties());
// then
if (result) {
Set<String> knownProperties = getAllKnownPropertyPaths();
List<String> missingProperties = new ArrayList<>();
for (String path : knownProperties) {
if (!resource.contains(path)) {
missingProperties.add(path);
}
}
fail("Found missing properties!\n-" + String.join("\n-", missingProperties));
}
}
@Test
public void shouldNotHaveUnknownConfigs() {
// given
File configFile = TestHelper.getJarFile(CONFIG_FILE);
FileConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
Map<String, Object> allReadProperties = configuration.getValues(true);
Set<String> knownKeys = getAllKnownPropertyPaths();
// when
List<String> unknownPaths = new ArrayList<>();
for (Map.Entry<String, Object> entry : allReadProperties.entrySet()) {
// The value being a MemorySection means it's a parent node
if (!(entry.getValue() instanceof MemorySection) && !knownKeys.contains(entry.getKey())) {
unknownPaths.add(entry.getKey());
}
}
// then
if (!unknownPaths.isEmpty()) {
fail("Found " + unknownPaths.size() + " unknown property paths in the project's config.yml: \n- "
+ String.join("\n- ", unknownPaths));
}
}
@Test
public void shouldHaveValueCorrespondingToPropertyDefault() {
// given
File configFile = TestHelper.getJarFile(CONFIG_FILE);
PropertyResource resource = new YamlFileResource(configFile);
ConfigurationData configurationData = AuthMeSettingsRetriever.buildConfigurationData();
// when / then
for (Property<?> property : configurationData.getProperties()) {
assertThat("Default value of '" + property.getPath() + "' in config.yml should be the same as in Property",
property.getValue(resource).equals(property.getDefaultValue()), equalTo(true));
}
}
private static Set<String> getAllKnownPropertyPaths() {
return AuthMeSettingsRetriever.buildConfigurationData()
.getProperties().stream()
.map(Property::getPath)
.collect(Collectors.toSet());
}
}
@@ -0,0 +1,69 @@
package fr.xephi.authme.settings;
import com.github.authme.configme.knownproperties.ConfigurationData;
import com.github.authme.configme.properties.Property;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.fail;
/**
* Tests the consistency of the settings configuration.
*/
public class SettingsConsistencyTest {
/**
* Maximum characters one comment line may have (prevents horizontal scrolling).
*/
private static final int MAX_COMMENT_LENGTH = 90;
private static ConfigurationData configurationData;
@BeforeClass
public static void buildConfigurationData() {
configurationData = AuthMeSettingsRetriever.buildConfigurationData();
}
@Test
public void shouldHaveCommentOnEachProperty() {
// given
List<Property<?>> properties = configurationData.getProperties();
// when / then
for (Property<?> property : properties) {
if (configurationData.getCommentsForSection(property.getPath()).length == 0) {
fail("No comment defined for '" + property + "'");
}
}
}
@Test
public void shouldNotHaveVeryLongCommentLines() {
// given
List<Property<?>> properties = configurationData.getProperties();
List<Property<?>> badProperties = new ArrayList<>();
// when
for (Property<?> property : properties) {
for (String comment : configurationData.getCommentsForSection(property.getPath())) {
if (comment.length() > MAX_COMMENT_LENGTH) {
badProperties.add(property);
break;
}
}
}
// then
if (!badProperties.isEmpty()) {
fail("Comment lines should not be longer than " + MAX_COMMENT_LENGTH + " chars, "
+ "but found too long comments for:\n- "
+ badProperties.stream().map(Property::getPath).collect(Collectors.joining("\n- ")));
}
}
}
@@ -1,67 +0,0 @@
package fr.xephi.authme.settings;
import com.github.authme.configme.knownproperties.ConfigurationData;
import com.github.authme.configme.resource.PropertyResource;
import com.github.authme.configme.resource.YamlFileResource;
import com.google.common.io.Files;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;
/**
* Test for {@link SettingsMigrationService}.
*/
public class SettingsMigrationServiceTest {
@Rule
public TemporaryFolder testFolderHandler = new TemporaryFolder();
private File testFolder;
private File configTestFile;
/**
* Ensure that AuthMe regards the JAR's own config.yml as complete.
* If something legitimately needs migrating, a test from {@link ConfigFileConsistencyTest} should fail.
* If none fails in that class, it means something is wrong with the migration service
* as it wants to perform a migration on our up-to-date config.yml.
*/
@Test
public void shouldNotRewriteJarConfig() throws IOException {
// given
copyConfigToTestFolder();
PropertyResource resource = new YamlFileResource(configTestFile);
ConfigurationData configurationData = AuthMeSettingsRetriever.buildConfigurationData();
assumeThat(testFolder.listFiles(), arrayWithSize(1));
SettingsMigrationService migrationService = new SettingsMigrationService(testFolder);
// when
boolean result = migrationService.checkAndMigrate(resource, configurationData.getProperties());
// then
assertThat(result, equalTo(false));
assertThat(testFolder.listFiles(), arrayWithSize(1));
}
private void copyConfigToTestFolder() throws IOException {
testFolder = testFolderHandler.newFolder("migrationtest");
final File testConfig = testFolderHandler.newFile("migrationtest/config.yml");
final File realConfig = TestHelper.getJarFile("/config.yml");
Files.copy(realConfig, testConfig);
if (!testConfig.exists()) {
throw new IOException("Could not copy project's config.yml to test folder");
}
configTestFile = testConfig;
}
}
@@ -11,6 +11,8 @@ import java.io.File;
import java.io.IOException;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
@@ -47,15 +49,15 @@ public class FileUtilsTest {
public void shouldCopyFileFromJar() throws IOException {
// given
File folder = temporaryFolder.newFolder();
File file = new File(folder, "some/folders/config.yml");
File file = new File(folder, "some/folders/welcome.txt");
// when
boolean result = FileUtils.copyFileFromResource(file, "config.yml");
boolean result = FileUtils.copyFileFromResource(file, "welcome.txt");
// then
assertThat(result, equalTo(true));
assertThat(file.exists(), equalTo(true));
File configJarFile = TestHelper.getJarFile("/config.yml");
File configJarFile = TestHelper.getJarFile("/welcome.txt");
assertThat(file.length(), equalTo(configJarFile.length()));
}
@@ -119,6 +121,13 @@ public class FileUtilsTest {
// Nothing happens
}
@Test
public void shouldGetResourceFromJar() {
// given / when / then
assertThat(FileUtils.getResourceFromJar("config.yml"), not(nullValue()));
assertThat(FileUtils.getResourceFromJar("does-not-exist"), nullValue());
}
@Test
public void shouldConstructPath() {
// given/when
@@ -12,7 +12,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.stream.Collectors;
@@ -29,11 +28,6 @@ public class CheckTestMocks implements AutoToolTask {
return "checkTestMocks";
}
@Override
public void execute(Scanner scanner) {
executeDefault();
}
@Override
public void executeDefault() {
ClassCollector collector = new ClassCollector(TestHelper.TEST_SOURCES_FOLDER, TestHelper.PROJECT_ROOT);
+14 -21
View File
@@ -1,26 +1,20 @@
package tools.docs;
import com.google.common.collect.ImmutableSet;
import tools.docs.commands.CommandPageCreater;
import tools.docs.hashmethods.HashAlgorithmsDescriptionTask;
import tools.docs.permissions.PermissionsListWriter;
import tools.docs.translations.TranslationPageGenerator;
import fr.xephi.authme.ClassCollector;
import fr.xephi.authme.TestHelper;
import tools.utils.AutoToolTask;
import tools.utils.ToolTask;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Task that runs all tasks which update files in the docs folder.
*/
public class UpdateDocsTask implements AutoToolTask {
private static final Set<Class<? extends ToolTask>> TASKS = ImmutableSet
.of(CommandPageCreater.class, HashAlgorithmsDescriptionTask.class,
PermissionsListWriter.class, TranslationPageGenerator.class);
@Override
public String getTaskName() {
return "updateDocs";
@@ -40,19 +34,18 @@ public class UpdateDocsTask implements AutoToolTask {
});
}
private static ToolTask instantiateTask(Class<? extends ToolTask> clazz) {
try {
return clazz.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
throw new UnsupportedOperationException("Could not instantiate task class '" + clazz + "'", e);
}
}
private static void executeTasks(Consumer<ToolTask> taskRunner) {
for (Class<? extends ToolTask> taskClass : TASKS) {
ToolTask task = instantiateTask(taskClass);
private void executeTasks(Consumer<ToolTask> taskRunner) {
for (ToolTask task : getDocTasks()) {
System.out.println("\nRunning " + task.getTaskName() + "\n-------------------");
taskRunner.accept(task);
}
}
private List<ToolTask> getDocTasks() {
ClassCollector classCollector =
new ClassCollector(TestHelper.TEST_SOURCES_FOLDER, "tools/docs");
return classCollector.getInstancesOfType(ToolTask.class).stream()
.filter(task -> task.getClass() != getClass())
.collect(Collectors.toList());
}
}
@@ -6,14 +6,12 @@ import fr.xephi.authme.command.CommandInitializer;
import fr.xephi.authme.command.CommandUtils;
import fr.xephi.authme.permission.PermissionNode;
import tools.utils.AutoToolTask;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.TagValue.NestedTagValue;
import tools.utils.TagValueHolder;
import tools.utils.ToolsConstants;
import java.util.Collection;
import java.util.Scanner;
import java.util.Set;
public class CommandPageCreater implements AutoToolTask {
@@ -24,19 +22,14 @@ public class CommandPageCreater implements AutoToolTask {
return "createCommandPage";
}
@Override
public void execute(Scanner scanner) {
executeDefault();
}
@Override
public void executeDefault() {
CommandInitializer commandInitializer = new CommandInitializer();
final Set<CommandDescription> baseCommands = commandInitializer.getCommands();
final Collection<CommandDescription> baseCommands = commandInitializer.getCommands();
NestedTagValue commandTags = new NestedTagValue();
addCommandsInfo(commandTags, baseCommands);
FileUtils.generateFileFromTemplate(
FileIoUtils.generateFileFromTemplate(
ToolsConstants.TOOLS_SOURCE_ROOT + "docs/commands/commands.tpl.md",
OUTPUT_FILE,
TagValueHolder.create().put("commands", commandTags));
@@ -1,5 +1,5 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See commands/commands.tpl.md -->
<!-- File auto-generated on {gen_date}. See docs/commands/commands.tpl.md -->
## AuthMe Commands
You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >`
@@ -0,0 +1,49 @@
package tools.docs.config;
import com.github.authme.configme.SettingsManager;
import com.github.authme.configme.resource.YamlFileResource;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import fr.xephi.authme.util.FileUtils;
import tools.utils.AutoToolTask;
import tools.utils.FileIoUtils;
import tools.utils.TagValueHolder;
import tools.utils.ToolsConstants;
import java.io.File;
import java.io.IOException;
/**
* Task for updating the config docs page.
*/
public class UpdateConfigPageTask implements AutoToolTask {
private static final String TEMPLATE_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "docs/config/config.tpl.md";
private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "config.md";
@Override
public String getTaskName() {
return "updateConfigPage";
}
@Override
public void executeDefault() {
File config = null;
try {
// Create empty temporary .yml file and save the config to it
config = File.createTempFile("authme-config-", ".yml");
SettingsManager settingsManager = new SettingsManager(
new YamlFileResource(config), null, AuthMeSettingsRetriever.buildConfigurationData());
settingsManager.save();
// Get the contents and generate template file
TagValueHolder tagValueHolder = TagValueHolder.create()
.put("config", FileIoUtils.readFromFile(config.toPath()));
FileIoUtils.generateFileFromTemplate(TEMPLATE_FILE, OUTPUT_FILE, tagValueHolder);
System.out.println("Wrote to '" + OUTPUT_FILE + "'");
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
FileUtils.delete(config);
}
}
}
@@ -0,0 +1,16 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See docs/config/config.tpl.md -->
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
with which you can configure various settings. This following is the initial contents of
the generated config.yml file.
```yml
{config}
```
To change settings on a running server, save your changes to config.yml and use
`/authme reload`.
{gen_footer}
@@ -2,13 +2,12 @@ package tools.docs.hashmethods;
import fr.xephi.authme.security.HashAlgorithm;
import tools.utils.AutoToolTask;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.TagValue.NestedTagValue;
import tools.utils.TagValueHolder;
import tools.utils.ToolsConstants;
import java.util.Map;
import java.util.Scanner;
/**
* Task for generating the markdown page describing the AuthMe hash algorithms.
@@ -20,11 +19,6 @@ public class HashAlgorithmsDescriptionTask implements AutoToolTask {
private static final String CUR_FOLDER = ToolsConstants.TOOLS_SOURCE_ROOT + "docs/hashmethods/";
private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "hash_algorithms.md";
@Override
public void execute(Scanner scanner) {
executeDefault();
}
@Override
public void executeDefault() {
// Gather info and construct a row for each method
@@ -34,7 +28,8 @@ public class HashAlgorithmsDescriptionTask implements AutoToolTask {
// Write to the docs file
TagValueHolder tags = TagValueHolder.create().put("algorithms", methodRows);
FileUtils.generateFileFromTemplate(CUR_FOLDER + "hash_algorithms.tpl.md", OUTPUT_FILE, tags);
FileIoUtils.generateFileFromTemplate(CUR_FOLDER + "hash_algorithms.tpl.md", OUTPUT_FILE, tags);
System.out.println("Wrote to '" + OUTPUT_FILE + "'");
}
private static NestedTagValue constructMethodRows(Map<HashAlgorithm, MethodDescription> descriptions) {
@@ -1,5 +1,5 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See hashmethods/hash_algorithms.tpl.md -->
<!-- File auto-generated on {gen_date}. See docs/hashmethods/hash_algorithms.tpl.md -->
## Hash Algorithms
AuthMe supports the following hash algorithms for storing your passwords safely.
@@ -2,15 +2,17 @@ package tools.docs.permissions;
import fr.xephi.authme.ClassCollector;
import fr.xephi.authme.permission.PermissionNode;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.ToolsConstants;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Gatherer to generate up-to-date lists of the AuthMe permission nodes.
@@ -27,6 +29,11 @@ public class PermissionNodesGatherer {
+ "(.*?)\\s+\\*/" // Capture everything until we encounter '*/'
+ "\\s+([A-Z_]+)\\("); // Match the enum name (e.g. 'LOGIN'), until before the first '('
/**
* List of all enum classes that implement the {@link PermissionNode} interface.
*/
private List<Class<? extends PermissionNode>> permissionClasses;
/**
* Return a sorted collection of all permission nodes, including its JavaDoc description.
*
@@ -39,14 +46,27 @@ public class PermissionNodesGatherer {
result.put("authme.player.*", "Permission to use all player (non-admin) commands.");
result.put("authme.player.email", "Grants all email permissions.");
new ClassCollector(ToolsConstants.MAIN_SOURCE_ROOT, "")
.collectClasses(PermissionNode.class)
.stream()
.filter(Class::isEnum)
.forEach(clz -> addDescriptionsForClass((Class<T>) clz, result));
getPermissionClasses().forEach(clz -> addDescriptionsForClass((Class<T>) clz, result));
return result;
}
/**
* Return all enum classes implementing the PermissionNode interface.
*
* @return all permission node enums
*/
public List<Class<? extends PermissionNode>> getPermissionClasses() {
if (permissionClasses == null) {
ClassCollector classCollector = new ClassCollector(ToolsConstants.MAIN_SOURCE_ROOT, "");
permissionClasses = classCollector
.collectClasses(PermissionNode.class)
.stream()
.filter(Class::isEnum)
.collect(Collectors.toList());
}
return permissionClasses;
}
private <T extends Enum<T> & PermissionNode> void addDescriptionsForClass(Class<T> clazz,
Map<String, String> descriptions) {
String classSource = getSourceForClass(clazz);
@@ -83,7 +103,7 @@ public class PermissionNodesGatherer {
*/
private static <T extends Enum<T> & PermissionNode> String getSourceForClass(Class<T> clazz) {
String classFile = ToolsConstants.MAIN_SOURCE_ROOT + clazz.getName().replace(".", "/") + ".java";
return FileUtils.readFromFile(classFile);
return FileIoUtils.readFromFile(classFile);
}
}
@@ -1,13 +1,12 @@
package tools.docs.permissions;
import tools.utils.AutoToolTask;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.TagValue.NestedTagValue;
import tools.utils.TagValueHolder;
import tools.utils.ToolsConstants;
import java.util.Map;
import java.util.Scanner;
/**
* Task responsible for formatting a permissions node list and
@@ -23,23 +22,13 @@ public class PermissionsListWriter implements AutoToolTask {
return "writePermissionsList";
}
@Override
public void execute(Scanner scanner) {
generateAndWriteFile();
}
@Override
public void executeDefault() {
generateAndWriteFile();
}
private static void generateAndWriteFile() {
final NestedTagValue permissionsTagValue = generatePermissionsList();
TagValueHolder tags = TagValueHolder.create().put("nodes", permissionsTagValue);
FileUtils.generateFileFromTemplate(TEMPLATE_FILE, PERMISSIONS_OUTPUT_FILE, tags);
FileIoUtils.generateFileFromTemplate(TEMPLATE_FILE, PERMISSIONS_OUTPUT_FILE, tags);
System.out.println("Wrote to '" + PERMISSIONS_OUTPUT_FILE + "'");
System.out.println("Before committing, please verify the output!");
}
private static NestedTagValue generatePermissionsList() {
@@ -1,5 +1,5 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See permissions/permission_nodes.tpl.md -->
<!-- File auto-generated on {gen_date}. See docs/permissions/permission_nodes.tpl.md -->
## AuthMe Permission Nodes
The following are the permission nodes that are currently supported by the latest dev builds.
@@ -3,14 +3,13 @@ package tools.docs.translations;
import com.google.common.collect.ImmutableMap;
import tools.docs.translations.TranslationsGatherer.TranslationInfo;
import tools.utils.AutoToolTask;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.TagValue.NestedTagValue;
import tools.utils.TagValueHolder;
import tools.utils.ToolsConstants;
import java.util.Arrays;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;
import static com.google.common.base.Objects.firstNonNull;
@@ -42,11 +41,6 @@ public class TranslationPageGenerator implements AutoToolTask {
return "updateTranslations";
}
@Override
public void execute(Scanner scanner) {
executeDefault();
}
@Override
public void executeDefault() {
NestedTagValue translationValuesHolder = new NestedTagValue();
@@ -63,7 +57,8 @@ public class TranslationPageGenerator implements AutoToolTask {
}
TagValueHolder tags = TagValueHolder.create().put("languages", translationValuesHolder);
FileUtils.generateFileFromTemplate(TEMPLATE_FILE, DOCS_PAGE, tags);
FileIoUtils.generateFileFromTemplate(TEMPLATE_FILE, DOCS_PAGE, tags);
System.out.println("Wrote to '" + DOCS_PAGE + "'");
}
/**
@@ -1,5 +1,5 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See translations/translations.tpl.md -->
<!-- File auto-generated on {gen_date}. See docs/translations/translations.tpl.md -->
# AuthMe Translations
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
@@ -0,0 +1,176 @@
package tools.filegeneration;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.CommandInitializer;
import fr.xephi.authme.command.CommandUtils;
import fr.xephi.authme.permission.DefaultPermission;
import fr.xephi.authme.permission.PermissionNode;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import tools.docs.permissions.PermissionNodesGatherer;
import tools.utils.AutoToolTask;
import tools.utils.FileIoUtils;
import tools.utils.ToolsConstants;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* Generates the command and permission section of plugin.yml.
*/
public class GeneratePluginYml implements AutoToolTask {
private static final String PLUGIN_YML_FILE = ToolsConstants.MAIN_RESOURCES_ROOT + "plugin.yml";
private static final Map<String, String> WILDCARD_PERMISSIONS = ImmutableMap.of(
"authme.player.*", "Gives access to all player commands",
"authme.admin.*", "Gives access to all admin commands",
"authme.player.email", "Gives access to all email commands");
private List<PermissionNode> permissionNodes;
private String pluginYmlStart;
@Override
public void executeDefault() {
FileConfiguration configuration = loadPartialPluginYmlFile();
configuration.set("commands", generateCommands());
configuration.set("permissions", generatePermissions());
FileIoUtils.writeToFile(PLUGIN_YML_FILE,
pluginYmlStart + "\n" + configuration.saveToString());
}
@Override
public String getTaskName() {
return "generatePluginYml";
}
/**
* Because some parts above the commands section have placeholders that aren't valid YAML, we need
* to split the contents into an upper part that we ignore and a lower part we load as YAML. When
* saving we prepend the YAML export with the stripped off part of the file again.
*
* @return file configuration with the lower part of the plugin.yml file
*/
private FileConfiguration loadPartialPluginYmlFile() {
List<String> pluginYmlLines = FileIoUtils.readLinesFromFile(Paths.get(PLUGIN_YML_FILE));
int lineNr = 0;
for (String line : pluginYmlLines) {
if (line.equals("commands:")) {
break;
}
++lineNr;
}
if (lineNr == pluginYmlLines.size()) {
throw new IllegalStateException("Could not find line starting 'commands:' section");
}
pluginYmlStart = String.join("\n", pluginYmlLines.subList(0, lineNr));
String yamlContents = String.join("\n", pluginYmlLines.subList(lineNr, pluginYmlLines.size()));
return YamlConfiguration.loadConfiguration(new StringReader(yamlContents));
}
private static Map<String, Object> generateCommands() {
Collection<CommandDescription> commands = new CommandInitializer().getCommands();
Map<String, Object> entries = new LinkedHashMap<>();
for (CommandDescription command : commands) {
entries.put(command.getLabels().get(0), buildCommandEntry(command));
}
return entries;
}
private Map<String, Object> generatePermissions() {
PermissionNodesGatherer gatherer = new PermissionNodesGatherer();
Map<String, String> permissionDescriptions = gatherer.gatherNodesWithJavaDoc();
permissionNodes = gatherer.getPermissionClasses().stream()
// Note ljacqu 20161023: The compiler fails if we use method references below
.map(clz -> clz.getEnumConstants())
.flatMap((PermissionNode[] nodes) -> Arrays.stream(nodes))
.collect(Collectors.toList());
Map<String, Object> descriptions = new TreeMap<>();
for (PermissionNode node : permissionNodes) {
descriptions.put(node.getNode(), buildPermissionEntry(node, permissionDescriptions.get(node.getNode())));
}
addWildcardPermissions(descriptions);
return descriptions;
}
private void addWildcardPermissions(Map<String, Object> permissions) {
for (Map.Entry<String, String> entry : WILDCARD_PERMISSIONS.entrySet()) {
permissions.put(entry.getKey(),
buildWildcardPermissionEntry(entry.getValue(), gatherChildren(entry.getKey())));
}
}
private Map<String, Boolean> gatherChildren(String parentNode) {
String parentPath = parentNode.replaceAll("\\.\\*$", "");
Map<String, Boolean> children = new TreeMap<>();
for (PermissionNode node : permissionNodes) {
if (node.getNode().startsWith(parentPath)) {
children.put(node.getNode(), Boolean.TRUE);
}
}
return children;
}
private static Map<String, Object> buildCommandEntry(CommandDescription command) {
if (command.getLabels().size() > 1) {
return ImmutableMap.of(
"description", command.getDescription(),
"usage", buildUsage(command),
"aliases", command.getLabels().subList(1, command.getLabels().size()));
} else {
return ImmutableMap.of(
"description", command.getDescription(),
"usage", buildUsage(command));
}
}
private static String buildUsage(CommandDescription command) {
if (!command.getArguments().isEmpty()) {
return CommandUtils.buildSyntax(command);
}
final String commandStart = "/" + command.getLabels().get(0);
String usage = commandStart + " " + command.getChildren()
.stream()
.filter(cmd -> !cmd.getLabels().contains("help"))
.map(cmd -> cmd.getLabels().get(0))
.collect(Collectors.joining("|"));
return usage.trim();
}
private static Map<String, Object> buildPermissionEntry(PermissionNode permissionNode, String description) {
return ImmutableMap.of(
"description", description,
"default", convertDefaultPermission(permissionNode.getDefaultPermission()));
}
private static Map<String, Object> buildWildcardPermissionEntry(String description, Map<String, Boolean> children) {
return ImmutableMap.of(
"description", description,
"children", children);
}
private static Object convertDefaultPermission(DefaultPermission defaultPermission) {
switch (defaultPermission) {
// Returning true/false as booleans will make SnakeYAML avoid using quotes
case ALLOWED: return true;
case NOT_ALLOWED: return false;
case OP_ONLY: return "op";
default:
throw new IllegalArgumentException("Unknown default permission '" + defaultPermission + "'");
}
}
}
@@ -0,0 +1,171 @@
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() {
// All entries start with "command.", so remove that
return missingCommands.stream()
.map(s -> s.substring(9)).collect(Collectors.toList());
}
public List<String> getUnknownCommands() {
// All entries start with "command.", so remove that
return unknownCommands.stream()
.map(s -> s.substring(9)).collect(Collectors.toList());
}
/**
* 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); // commandPaths should be empty in this case
} else {
missingCommands.addAll(Sets.difference(commandPaths, existingKeys));
unknownCommands.addAll(Sets.difference(existingKeys, commandPaths));
}
}
private 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 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("."));
// The entire command is not present, so just add it as a missing command and don't return any YAML path
if (!configuration.contains(commandPath)) {
missingCommands.add(commandPath);
return Collections.emptyList();
}
// 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());
}
}
@@ -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;
}
}
@@ -8,7 +8,7 @@ import com.google.common.collect.Multimap;
import fr.xephi.authme.message.MessageKey;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import java.io.File;
import java.util.ArrayList;
@@ -104,7 +104,7 @@ public class MessageFileVerifier {
* @param defaultMessages The collection of default messages
*/
public void addMissingKeys(FileConfiguration defaultMessages) {
final List<String> fileLines = FileUtils.readLinesFromFile(messagesFile.toPath());
final List<String> fileLines = FileIoUtils.readLinesFromFile(messagesFile.toPath());
List<MissingKey> keysToAdd = new ArrayList<>();
for (MissingKey entry : missingKeys) {
@@ -135,7 +135,7 @@ public class MessageFileVerifier {
addCommentForMissingTags(fileLines, key, entry.getValue());
}
FileUtils.writeToFile(messagesFile.toPath(), String.join("\n", fileLines));
FileIoUtils.writeToFile(messagesFile.toPath(), String.join("\n", fileLines));
}
/**
@@ -7,7 +7,7 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import tools.messages.MessageFileVerifier;
import tools.messages.VerifyMessagesTask;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.ToolTask;
import tools.utils.ToolsConstants;
@@ -108,9 +108,9 @@ public class ImportMessagesTask implements ToolTask {
* @param file The file whose to-do comments should be removed
*/
private static void removeAllTodoComments(String file) {
String contents = FileUtils.readFromFile(file);
String contents = FileIoUtils.readFromFile(file);
String regex = "^# TODO .*$";
contents = Pattern.compile(regex, Pattern.MULTILINE).matcher(contents).replaceAll("");
FileUtils.writeToFile(file, contents);
FileIoUtils.writeToFile(file, contents);
}
}
@@ -2,7 +2,7 @@ package tools.messages.translation;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import tools.utils.FileUtils;
import tools.utils.FileIoUtils;
import tools.utils.ToolsConstants;
import java.io.File;
@@ -31,7 +31,7 @@ public class WriteAllExportsTask extends ExportMessagesTask {
for (File file : messageFiles) {
String code = file.getName().substring("messages_".length(), file.getName().length() - ".yml".length());
String json = convertToJson(code, defaultMessages, YamlConfiguration.loadConfiguration(file));
FileUtils.writeToFile(OUTPUT_FOLDER + "messages_" + code + ".json", json);
FileIoUtils.writeToFile(OUTPUT_FOLDER + "messages_" + code + ".json", json);
}
}
}
@@ -1,5 +1,7 @@
package tools.utils;
import java.util.Scanner;
/**
* Interface for tasks that can be run automatically, i.e. without any user input.
*/
@@ -10,4 +12,9 @@ public interface AutoToolTask extends ToolTask {
*/
void executeDefault();
@Override
default void execute(Scanner scanner) {
executeDefault();
}
}
@@ -9,11 +9,11 @@ import java.nio.file.StandardOpenOption;
import java.util.List;
/**
* Utility class for reading from and writing to files.
* Utility class for I/O operations on files.
*/
public final class FileUtils {
public final class FileIoUtils {
private FileUtils() {
private FileIoUtils() {
}
public static void generateFileFromTemplate(String templateFile, String destinationFile, TagValueHolder tags) {
@@ -43,8 +43,12 @@ public final class FileUtils {
}
public static String readFromFile(String file) {
return readFromFile(Paths.get(file));
}
public static String readFromFile(Path file) {
try {
return new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);
return new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UnsupportedOperationException("Could not read from file '" + file + "'", e);
}
@@ -9,9 +9,6 @@ public final class ToolsConstants {
public static final String MAIN_RESOURCES_ROOT = "src/main/resources/";
// Add specific `fr.xephi.authme` package as not to include the tool tasks in the `tools` package
public static final String TEST_SOURCE_ROOT = "src/test/java/fr/xephi/authme";
public static final String TOOLS_SOURCE_ROOT = "src/test/java/tools/";
public static final String DOCS_FOLDER = "docs/";