diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 60eec7905..7e01e0e30 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -1,6 +1,6 @@ package fr.xephi.authme.command; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; import fr.xephi.authme.command.executable.HelpCommand; import fr.xephi.authme.command.executable.authme.AccountsCommand; import fr.xephi.authme.command.executable.authme.AuthMeCommand; @@ -41,14 +41,13 @@ import fr.xephi.authme.permission.PlayerPermission; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Set; /** * Initializes all available AuthMe commands. */ public class CommandInitializer { - private Set commands; + private List commands; public CommandInitializer() { buildCommands(); @@ -59,7 +58,7 @@ public class CommandInitializer { * * @return the command descriptions */ - public Set getCommands() { + public List getCommands() { return commands; } @@ -67,7 +66,7 @@ public class CommandInitializer { // Register the base AuthMe Reloaded command final CommandDescription AUTHME_BASE = CommandDescription.builder() .labels("authme") - .description("Main command") + .description("AuthMe op commands") .detailedDescription("The main AuthMeReloaded command. The root for all admin commands.") .executableCommand(AuthMeCommand.class) .register(); @@ -324,7 +323,7 @@ public class CommandInitializer { final CommandDescription REGISTER_BASE = CommandDescription.builder() .parent(null) .labels("register", "reg") - .description("Registration command") + .description("Register an account") .detailedDescription("Command to register using AuthMeReloaded.") .withArgument("password", "Password", true) .withArgument("verifyPassword", "Verify password", true) @@ -336,7 +335,7 @@ public class CommandInitializer { CommandDescription UNREGISTER_BASE = CommandDescription.builder() .parent(null) .labels("unregister", "unreg") - .description("Unregistration Command") + .description("Unregister an account") .detailedDescription("Command to unregister using AuthMeReloaded.") .withArgument("password", "Password", false) .permission(PlayerPermission.UNREGISTER) @@ -347,10 +346,10 @@ public class CommandInitializer { final CommandDescription CHANGE_PASSWORD_BASE = CommandDescription.builder() .parent(null) .labels("changepassword", "changepass", "cp") - .description("Change password Command") + .description("Change password of an account") .detailedDescription("Command to change your password using AuthMeReloaded.") - .withArgument("oldPassword", "Old Password", false) - .withArgument("newPassword", "New Password.", false) + .withArgument("oldPassword", "Old password", false) + .withArgument("newPassword", "New password", false) .permission(PlayerPermission.CHANGE_PASSWORD) .executableCommand(ChangePasswordCommand.class) .register(); @@ -359,8 +358,8 @@ public class CommandInitializer { CommandDescription EMAIL_BASE = CommandDescription.builder() .parent(null) .labels("email") - .description("Email command") - .detailedDescription("The AuthMeReloaded Email command base.") + .description("Add email or recover password") + .detailedDescription("The AuthMeReloaded email command base.") .executableCommand(EmailBaseCommand.class) .register(); @@ -401,7 +400,7 @@ public class CommandInitializer { CommandDescription.builder() .parent(EMAIL_BASE) .labels("recover", "recovery", "recoveremail", "recovermail") - .description("Recover password using Email") + .description("Recover password using email") .detailedDescription("Recover your account using an Email address by sending a mail containing " + "a new password.") .withArgument("email", "Email address", false) @@ -421,7 +420,7 @@ public class CommandInitializer { .executableCommand(CaptchaCommand.class) .register(); - Set baseCommands = ImmutableSet.of( + List baseCommands = ImmutableList.of( AUTHME_BASE, LOGIN_BASE, LOGOUT_BASE, diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java index 44d5b5833..e36664929 100644 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -8,6 +8,7 @@ import org.bukkit.command.CommandSender; import javax.inject.Inject; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -26,7 +27,7 @@ public class CommandMapper { */ private static final Class HELP_COMMAND_CLASS = HelpCommand.class; - private final Set baseCommands; + private final Collection baseCommands; private final PermissionsManager permissionsManager; @Inject diff --git a/src/main/java/fr/xephi/authme/command/CommandUtils.java b/src/main/java/fr/xephi/authme/command/CommandUtils.java index 9ea1f5c55..699e4f37e 100644 --- a/src/main/java/fr/xephi/authme/command/CommandUtils.java +++ b/src/main/java/fr/xephi/authme/command/CommandUtils.java @@ -1,9 +1,11 @@ package fr.xephi.authme.command; import com.google.common.collect.Lists; +import org.bukkit.ChatColor; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public final class CommandUtils { @@ -34,6 +36,14 @@ public final class CommandUtils { return sb.toString(); } + /** + * Constructs a hierarchical list of commands for the given command. The commands are in order: + * the parents of the given command precede the provided command. For example, given the command + * for {@code /authme register}, a list with {@code [{authme}, {authme register}]} is returned. + * + * @param command the command to build a parent list for + * @return the parent list + */ public static List constructParentList(CommandDescription command) { List commands = new ArrayList<>(); CommandDescription currentCommand = command; @@ -43,4 +53,35 @@ public final class CommandUtils { } return Lists.reverse(commands); } + + public static String buildSyntax(CommandDescription command) { + String arguments = command.getArguments().stream() + .map(arg -> formatArgument(arg)) + .collect(Collectors.joining(" ")); + return (constructCommandPath(command) + " " + arguments).trim(); + } + + public static String buildSyntax(CommandDescription command, List correctLabels) { + String commandSyntax = ChatColor.WHITE + "/" + correctLabels.get(0) + ChatColor.YELLOW; + for (int i = 1; i < correctLabels.size(); ++i) { + commandSyntax += " " + correctLabels.get(i); + } + for (CommandArgumentDescription argument : command.getArguments()) { + commandSyntax += " " + formatArgument(argument); + } + return commandSyntax; + } + + /** + * Format a command argument with the proper type of brackets. + * + * @param argument the argument to format + * @return the formatted argument + */ + public static String formatArgument(CommandArgumentDescription argument) { + if (argument.isOptional()) { + return "[" + argument.getName() + "]"; + } + return "<" + argument.getName() + ">"; + } } diff --git a/src/main/java/fr/xephi/authme/command/help/CommandSyntaxHelper.java b/src/main/java/fr/xephi/authme/command/help/CommandSyntaxHelper.java deleted file mode 100644 index 86ffb5427..000000000 --- a/src/main/java/fr/xephi/authme/command/help/CommandSyntaxHelper.java +++ /dev/null @@ -1,36 +0,0 @@ -package fr.xephi.authme.command.help; - -import fr.xephi.authme.command.CommandArgumentDescription; -import fr.xephi.authme.command.CommandDescription; -import org.bukkit.ChatColor; - -import java.util.List; - -/** - * Helper class for displaying the syntax of a command properly to a user. - */ -final class CommandSyntaxHelper { - - private CommandSyntaxHelper() { - } - - public static String getSyntax(CommandDescription command, List correctLabels) { - String commandSyntax = ChatColor.WHITE + "/" + correctLabels.get(0) + ChatColor.YELLOW; - for (int i = 1; i < correctLabels.size(); ++i) { - commandSyntax += " " + correctLabels.get(i); - } - for (CommandArgumentDescription argument : command.getArguments()) { - commandSyntax += " " + formatArgument(argument); - } - return commandSyntax; - } - - /** Format a command argument with the proper type of brackets. */ - private static String formatArgument(CommandArgumentDescription argument) { - if (argument.isOptional()) { - return "[" + argument.getName() + "]"; - } - return "<" + argument.getName() + ">"; - } - -} diff --git a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java index 413c42c10..23e87cab6 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java @@ -80,7 +80,7 @@ public class HelpProvider implements Reloadable { if (hasFlag(SHOW_COMMAND, options)) { lines.add(ChatColor.GOLD + helpMessagesService.getMessage(HelpSection.COMMAND) + ": " - + CommandSyntaxHelper.getSyntax(command, correctLabels)); + + CommandUtils.buildSyntax(command, correctLabels)); } if (hasFlag(SHOW_DESCRIPTION, options)) { lines.add(ChatColor.GOLD + helpMessagesService.getMessage(SHORT_DESCRIPTION) + ": " @@ -194,7 +194,7 @@ public class HelpProvider implements Reloadable { // Create a list of alternatives for (String label : command.getLabels()) { if (!label.equalsIgnoreCase(usedLabel)) { - lines.add(" " + CommandSyntaxHelper.getSyntax(command, commandLabelsFn.apply(label))); + lines.add(" " + CommandUtils.buildSyntax(command, commandLabelsFn.apply(label))); } } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 90ee29bff..9ca68c314 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -15,191 +15,197 @@ softdepend: - EssentialsSpawn - ProtocolLib commands: - authme: - description: AuthMe op commands - usage: '/authme reload|register playername password|changepassword playername password|unregister playername|version|converter' - register: - description: Register an account - usage: /register - aliases: [reg] - login: - description: Login command - usage: /login - aliases: [l,log] - changepassword: - description: Change password of an account - usage: /changepassword - aliases: [cp,changepass] - logout: - description: Logout - usage: /logout - unregister: - description: Unregister your account - usage: /unregister - aliases: [unreg] - email: - description: Add Email or recover password - usage: '/email add your@email.com your@email.com|change oldEmail newEmail|recovery your@email.com' - captcha: - description: Captcha command - usage: /captcha + authme: + description: AuthMe op commands + usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages + login: + description: Login command + usage: /login + aliases: + - l + - log + logout: + description: Logout command + usage: /logout + register: + description: Register an account + usage: /register [password] [verifyPassword] + aliases: + - reg + unregister: + description: Unregister an account + usage: /unregister + aliases: + - unreg + changepassword: + description: Change password of an account + usage: /changepassword + aliases: + - changepass + - cp + email: + description: Add email or recover password + usage: /email show|add|change|recover + captcha: + description: Captcha Command + usage: /captcha permissions: - authme.admin.*: - description: Give access to all admin commands. - children: - authme.admin.accounts: true - authme.admin.antibotmessages: true - authme.admin.changemail: true - authme.admin.changepassword: true - authme.admin.converter: true - authme.admin.firstspawn: true - authme.admin.forcelogin: true - authme.admin.getemail: true - authme.admin.getip: true - authme.admin.lastlogin: true - authme.admin.purge: true - authme.admin.purgebannedplayers: true - authme.admin.purgelastpos: true - authme.admin.register: true - authme.admin.reload: true - authme.admin.setfirstspawn: true - authme.admin.setspawn: true - authme.admin.spawn: true - authme.admin.switchantibot: true - authme.admin.unregister: true - authme.admin.updatemessages: true - authme.admin.register: - description: Administrator command to register a new user. - default: op - authme.admin.unregister: - description: Administrator command to unregister an existing user. - default: op - authme.admin.forcelogin: - description: Administrator command to force-login an existing user. - default: op - authme.admin.changepassword: - description: Administrator command to change the password of a user. - default: op - authme.admin.lastlogin: - description: Administrator command to see the last login date and time of a user. - default: op - authme.admin.accounts: - description: Administrator command to see all accounts associated with a user. - default: op - authme.admin.getemail: - description: Administrator command to get the email address of a user, if set. - default: op - authme.admin.changemail: - description: Administrator command to set or change the email address of a user. - default: op - authme.admin.getip: - description: Administrator command to get the last known IP of a user. - default: op - authme.admin.spawn: - description: Administrator command to teleport to the AuthMe spawn. - default: op - authme.admin.setspawn: - description: Administrator command to set the AuthMe spawn. - default: op - authme.admin.firstspawn: - description: Administrator command to teleport to the first AuthMe spawn. - default: op - authme.admin.setfirstspawn: - description: Administrator command to set the first AuthMe spawn. - default: op - authme.admin.purge: - description: Administrator command to purge old user data. - default: op - authme.admin.purgelastpos: - description: Administrator command to purge the last position of a user. - default: op - authme.admin.purgebannedplayers: - description: Administrator command to purge all data associated with banned players. - default: op - authme.admin.seeotheraccounts: - description: Permission for user to see other accounts. - default: op - authme.admin.switchantibot: - description: Administrator command to toggle the AntiBot protection status. - default: op - authme.admin.converter: - description: Administrator command to convert old or other data to AuthMe data. - default: op - authme.admin.reload: - description: Administrator command to reload the plugin configuration. - default: op - authme.admin.antibotmessages: - description: Permission to see Antibot messages. - default: op - authme.admin.updatemessages: - description: Permission to use the update messages command. - default: op - authme.player.*: - description: Permission to use all player (non-admin) commands. - children: - authme.player.canbeforced: true - authme.player.captcha: true - authme.player.changepassword: true - authme.player.email.add: true - authme.player.email.change: true - authme.player.email.recover: true - authme.player.login: true - authme.player.logout: true - authme.player.register: true - authme.player.unregister: true - authme.player.seeownaccounts: true - authme.player.email: - description: Gives access to player email commands - default: false - children: - authme.player.email.add: true - authme.player.email.change: true - authme.player.email.recover: true - authme.player.login: - description: Command permission to login. - default: true - authme.player.logout: - description: Command permission to logout. - default: true - authme.player.register: - description: Command permission to register. - default: true - authme.player.unregister: - description: Command permission to unregister. - default: true - authme.player.changepassword: - description: Command permission to change the password. - default: true - authme.player.email.add: - description: Command permission to add an email address. - default: true - authme.player.email.change: - description: Command permission to change the email address. - default: true - authme.player.email.recover: - description: Command permission to recover an account using its email address. - default: true - authme.player.captcha: - description: Command permission to use captcha. - default: true - authme.player.canbeforced: - description: Permission for users a login can be forced to. - default: true - authme.player.seeownaccounts: - description: Permission to use to see own other accounts. - default: true - authme.vip: - description: Allow vip slot when the server is full - default: op - authme.bypassantibot: - description: Bypass the AntiBot check - default: op - authme.allowmultipleaccounts: - description: Allow more accounts for same ip - default: op - authme.bypassforcesurvival: - description: Bypass all ForceSurvival features - default: op - authme.bypasspurge: - description: Permission to bypass the purging process - default: false + authme.admin.*: + description: Gives access to all admin commands + children: + authme.admin.accounts: true + authme.admin.antibotmessages: true + authme.admin.changemail: true + authme.admin.changepassword: true + authme.admin.converter: true + authme.admin.firstspawn: true + authme.admin.forcelogin: true + authme.admin.getemail: true + authme.admin.getip: true + authme.admin.lastlogin: true + authme.admin.purge: true + authme.admin.purgebannedplayers: true + authme.admin.purgelastpos: true + authme.admin.register: true + authme.admin.reload: true + authme.admin.seeotheraccounts: true + authme.admin.setfirstspawn: true + authme.admin.setspawn: true + authme.admin.spawn: true + authme.admin.switchantibot: true + authme.admin.unregister: true + authme.admin.updatemessages: true + authme.admin.accounts: + description: Administrator command to see all accounts associated with a user. + default: op + authme.admin.antibotmessages: + description: Permission to see Antibot messages. + default: op + authme.admin.changemail: + description: Administrator command to set or change the email address of a user. + default: op + authme.admin.changepassword: + description: Administrator command to change the password of a user. + default: op + authme.admin.converter: + description: Administrator command to convert old or other data to AuthMe data. + default: op + authme.admin.firstspawn: + description: Administrator command to teleport to the first AuthMe spawn. + default: op + authme.admin.forcelogin: + description: Administrator command to force-login an existing user. + default: op + authme.admin.getemail: + description: Administrator command to get the email address of a user, if set. + default: op + authme.admin.getip: + description: Administrator command to get the last known IP of a user. + default: op + authme.admin.lastlogin: + description: Administrator command to see the last login date and time of a user. + default: op + authme.admin.purge: + description: Administrator command to purge old user data. + default: op + authme.admin.purgebannedplayers: + description: Administrator command to purge all data associated with banned players. + default: op + authme.admin.purgelastpos: + description: Administrator command to purge the last position of a user. + default: op + authme.admin.register: + description: Administrator command to register a new user. + default: op + authme.admin.reload: + description: Administrator command to reload the plugin configuration. + default: op + authme.admin.seeotheraccounts: + description: Permission to see the other accounts of the players that log in. + default: op + authme.admin.setfirstspawn: + description: Administrator command to set the first AuthMe spawn. + default: op + authme.admin.setspawn: + description: Administrator command to set the AuthMe spawn. + default: op + authme.admin.spawn: + description: Administrator command to teleport to the AuthMe spawn. + default: op + authme.admin.switchantibot: + description: Administrator command to toggle the AntiBot protection status. + default: op + authme.admin.unregister: + description: Administrator command to unregister an existing user. + default: op + authme.admin.updatemessages: + description: Permission to use the update messages command. + default: op + authme.allowmultipleaccounts: + description: Permission to be able to register multiple accounts. + default: op + authme.bypassantibot: + description: Permission node to bypass AntiBot protection. + default: op + authme.bypassforcesurvival: + description: Permission for users to bypass force-survival mode. + default: op + authme.bypasspurge: + description: Permission to bypass the purging process. + default: false + authme.player.*: + description: Gives access to all player commands + children: + authme.player.canbeforced: true + authme.player.captcha: true + authme.player.changepassword: true + authme.player.email.add: true + authme.player.email.change: true + authme.player.email.recover: true + authme.player.login: true + authme.player.logout: true + authme.player.register: true + authme.player.seeownaccounts: true + authme.player.unregister: true + authme.player.canbeforced: + description: Permission for users a login can be forced to. + default: true + authme.player.captcha: + description: Command permission to use captcha. + default: true + authme.player.changepassword: + description: Command permission to change the password. + default: true + authme.player.email: + description: Gives access to all email commands + children: + authme.player.email.add: true + authme.player.email.change: true + authme.player.email.recover: true + authme.player.email.add: + description: Command permission to add an email address. + default: true + authme.player.email.change: + description: Command permission to change the email address. + default: true + authme.player.email.recover: + description: Command permission to recover an account using its email address. + default: true + authme.player.login: + description: Command permission to login. + default: true + authme.player.logout: + description: Command permission to logout. + default: true + authme.player.register: + description: Command permission to register. + default: true + authme.player.seeownaccounts: + description: Permission to use to see own other accounts. + default: true + authme.player.unregister: + description: Command permission to unregister. + default: true + authme.vip: + description: Permission node to identify VIP users. + default: op diff --git a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java index 944424f52..a4e51e28c 100644 --- a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java @@ -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 commands; + private static Collection 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)); diff --git a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java index 3fd023a4e..46efdaec4 100644 --- a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java @@ -39,7 +39,7 @@ import static org.mockito.Mockito.mock; @RunWith(DelayedInjectionRunner.class) public class CommandMapperTest { - private static Set commands; + private static List commands; @InjectDelayed private CommandMapper mapper; diff --git a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java index 173a0919f..c0b4771cd 100644 --- a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java @@ -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 commands; + + @BeforeClass + public static void setUpTestCommands() { + commands = TestCommandsUtil.generateCommands(); + } + @Test public void shouldReturnCommandPath() { // given @@ -79,6 +93,46 @@ public class CommandUtilsTest { TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandUtils.class); } + @Test + public void shouldFormatSimpleArgument() { + // given + CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme"); + List 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 labels = Arrays.asList("authme", "reg"); + + // when + String result = CommandUtils.buildSyntax(command, labels); + + // then + assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW + " reg ")); + } + + + @Test + public void shouldFormatCommandWithOptionalArgument() { + // given + CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "email"); + List 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)); diff --git a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java index 9c5d469ab..9f9b02ca2 100644 --- a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java +++ b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java @@ -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 generateCommands() { + public static List generateCommands() { // Register /authme CommandDescription authMeBase = createCommand(null, null, singletonList("authme"), ExecutableCommand.class); // Register /authme login @@ -48,7 +47,7 @@ public final class TestCommandsUtil { 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); } /** diff --git a/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java b/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java deleted file mode 100644 index 77320d151..000000000 --- a/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java +++ /dev/null @@ -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 commands; - - @BeforeClass - public static void setUpTestCommands() { - commands = TestCommandsUtil.generateCommands(); - } - - @Test - public void shouldFormatSimpleArgument() { - // given - CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "authme"); - List 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 labels = Arrays.asList("authme", "reg"); - - // when - String result = CommandSyntaxHelper.getSyntax(command, labels); - - // then - assertThat(result, equalTo(ChatColor.WHITE + "/authme" + ChatColor.YELLOW + " reg ")); - } - - - @Test - public void shouldFormatCommandWithOptionalArgument() { - // given - CommandDescription command = TestCommandsUtil.getCommandWithLabel(commands, "email"); - List 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); - } - -} diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java index 7c50829f8..5e79ef993 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java @@ -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 commands = new CommandInitializer().getCommands(); + Collection commands = new CommandInitializer().getCommands(); List children = commands.stream() .filter(command -> command.getLabels().contains("authme")) diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java index 3a9c993d1..150bb42f5 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java @@ -12,7 +12,7 @@ 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; @@ -31,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 COMMANDS = TestCommandsUtil.generateCommands(); + private static final Collection COMMANDS = TestCommandsUtil.generateCommands(); @InjectDelayed private HelpMessagesService helpMessagesService; diff --git a/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java b/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java index 1e8962f8d..e90a76120 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java @@ -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 commands; + private static Collection commands; @InjectDelayed private HelpProvider helpProvider; diff --git a/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java b/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java index bd18df8ad..f44ef2220 100644 --- a/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java @@ -26,7 +26,7 @@ public class PermissionConsistencyTest { /** All classes defining permission nodes. */ private static final Set> PERMISSION_CLASSES = ImmutableSet - .>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 PLUGIN_YML_PERMISSIONS_WILDCARDS = diff --git a/src/test/java/tools/docs/commands/CommandPageCreater.java b/src/test/java/tools/docs/commands/CommandPageCreater.java index 6a2f5edfe..b975c34f5 100644 --- a/src/test/java/tools/docs/commands/CommandPageCreater.java +++ b/src/test/java/tools/docs/commands/CommandPageCreater.java @@ -13,7 +13,6 @@ import tools.utils.ToolsConstants; import java.util.Collection; import java.util.Scanner; -import java.util.Set; public class CommandPageCreater implements AutoToolTask { @@ -32,7 +31,7 @@ public class CommandPageCreater implements AutoToolTask { @Override public void executeDefault() { CommandInitializer commandInitializer = new CommandInitializer(); - final Set baseCommands = commandInitializer.getCommands(); + final Collection baseCommands = commandInitializer.getCommands(); NestedTagValue commandTags = new NestedTagValue(); addCommandsInfo(commandTags, baseCommands); diff --git a/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java b/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java index f6221ac5f..034677cb8 100644 --- a/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java +++ b/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java @@ -7,10 +7,12 @@ 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> 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) clz, result)); + getPermissionClasses().forEach(clz -> addDescriptionsForClass((Class) clz, result)); return result; } + /** + * Return all enum classes implementing the PermissionNode interface. + * + * @return all permission node enums + */ + public List> 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 & PermissionNode> void addDescriptionsForClass(Class clazz, Map descriptions) { String classSource = getSourceForClass(clazz); diff --git a/src/test/java/tools/filegeneration/GeneratePluginYml.java b/src/test/java/tools/filegeneration/GeneratePluginYml.java new file mode 100644 index 000000000..c3d332f24 --- /dev/null +++ b/src/test/java/tools/filegeneration/GeneratePluginYml.java @@ -0,0 +1,182 @@ +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.FileUtils; +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.Scanner; +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 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 permissionNodes; + + private String pluginYmlStart; + + @Override + public void executeDefault() { + FileConfiguration configuration = loadPartialPluginYmlFile(); + + configuration.set("commands", generateCommands()); + configuration.set("permissions", generatePermissions()); + + FileUtils.writeToFile(PLUGIN_YML_FILE, + pluginYmlStart + "\n" + configuration.saveToString()); + } + + @Override + public String getTaskName() { + return "generatePluginYml"; + } + + @Override + public void execute(Scanner scanner) { + executeDefault(); + } + + /** + * 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 pluginYmlLines = FileUtils.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 generateCommands() { + Collection commands = new CommandInitializer().getCommands(); + Map entries = new LinkedHashMap<>(); + for (CommandDescription command : commands) { + entries.put(command.getLabels().get(0), buildCommandEntry(command)); + } + return entries; + } + + private Map generatePermissions() { + PermissionNodesGatherer gatherer = new PermissionNodesGatherer(); + Map 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 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 permissions) { + for (Map.Entry entry : WILDCARD_PERMISSIONS.entrySet()) { + permissions.put(entry.getKey(), + buildWildcardPermissionEntry(entry.getValue(), gatherChildren(entry.getKey()))); + } + } + + private Map gatherChildren(String parentNode) { + String parentPath = parentNode.replaceAll("\\.\\*$", ""); + + Map children = new TreeMap<>(); + for (PermissionNode node : permissionNodes) { + if (node.getNode().startsWith(parentPath)) { + children.put(node.getNode(), Boolean.TRUE); + } + } + return children; + } + + private static Map 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 buildPermissionEntry(PermissionNode permissionNode, String description) { + return ImmutableMap.of( + "description", description, + "default", convertDefaultPermission(permissionNode.getDefaultPermission())); + } + + private static Map buildWildcardPermissionEntry(String description, Map 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 + "'"); + } + } +}