#963 Create tool task to generate plugin.yml

- Create task that generates commands/permissions section of plugin.yml
- Change CommandInitializer to return a List instead of Set (preserve insertion order)
- Merge CommandSyntaxHelper into CommandUtils
This commit is contained in:
ljacqu
2016-10-23 12:17:37 +02:00
parent 1867617dbb
commit d09964f1cb
18 changed files with 527 additions and 337 deletions
@@ -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<CommandDescription> baseCommands = commandInitializer.getCommands();
final Collection<CommandDescription> baseCommands = commandInitializer.getCommands();
NestedTagValue commandTags = new NestedTagValue();
addCommandsInfo(commandTags, baseCommands);
@@ -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<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);
@@ -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<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());
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<String> 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<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 + "'");
}
}
}