Move all doc generating tasks into docs package
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
package tools.docs;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import tools.commands.CommandPageCreater;
|
||||
import tools.hashmethods.HashAlgorithmsDescriptionTask;
|
||||
import tools.permissions.PermissionsListWriter;
|
||||
import tools.docs.commands.CommandPageCreater;
|
||||
import tools.docs.hashmethods.HashAlgorithmsDescriptionTask;
|
||||
import tools.docs.permissions.PermissionsListWriter;
|
||||
import tools.utils.AutoToolTask;
|
||||
import tools.utils.ToolTask;
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package tools.docs.commands;
|
||||
|
||||
import fr.xephi.authme.command.CommandArgumentDescription;
|
||||
import fr.xephi.authme.command.CommandDescription;
|
||||
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.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 {
|
||||
|
||||
private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "commands.md";
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "createCommandPage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Scanner scanner) {
|
||||
executeDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeDefault() {
|
||||
CommandInitializer commandInitializer = new CommandInitializer();
|
||||
final Set<CommandDescription> baseCommands = commandInitializer.getCommands();
|
||||
NestedTagValue commandTags = new NestedTagValue();
|
||||
addCommandsInfo(commandTags, baseCommands);
|
||||
|
||||
FileUtils.generateFileFromTemplate(
|
||||
ToolsConstants.TOOLS_SOURCE_ROOT + "docs/commands/commands.tpl.md",
|
||||
OUTPUT_FILE,
|
||||
TagValueHolder.create().put("commands", commandTags));
|
||||
System.out.println("Wrote to '" + OUTPUT_FILE + "' with " + baseCommands.size() + " base commands.");
|
||||
}
|
||||
|
||||
private static void addCommandsInfo(NestedTagValue commandTags, Collection<CommandDescription> commands) {
|
||||
for (CommandDescription command : commands) {
|
||||
TagValueHolder tags = TagValueHolder.create()
|
||||
.put("command", CommandUtils.constructCommandPath(command))
|
||||
.put("description", command.getDetailedDescription())
|
||||
.put("arguments", formatArguments(command.getArguments()))
|
||||
.put("permissions", formatPermissions(command.getPermission()));
|
||||
commandTags.add(tags);
|
||||
|
||||
if (!command.getChildren().isEmpty()) {
|
||||
addCommandsInfo(commandTags, command.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatPermissions(PermissionNode permission) {
|
||||
if (permission == null) {
|
||||
return "";
|
||||
} else {
|
||||
return permission.getNode();
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatArguments(Iterable<CommandArgumentDescription> arguments) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (CommandArgumentDescription argument : arguments) {
|
||||
String argumentName = argument.isOptional()
|
||||
? "[" + argument.getName() + "]"
|
||||
: "<" + argument.getName() + ">";
|
||||
result.append(" ").append(argumentName);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!-- {gen_warning} -->
|
||||
<!-- File auto-generated on {gen_date}. See commands/commands.tpl.md -->
|
||||
|
||||
## AuthMe Commands
|
||||
You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >`
|
||||
brackets; optional arguments are enclosed in square brackets (`[ ]`).
|
||||
|
||||
[#commands]
|
||||
- **{command}**{arguments}: {description}[permissions]
|
||||
<br />Requires `{permissions}`[/permissions]
|
||||
[/#commands]
|
||||
|
||||
{gen_footer}
|
||||
@@ -0,0 +1,159 @@
|
||||
package tools.docs.hashmethods;
|
||||
|
||||
import ch.jalu.injector.Injector;
|
||||
import ch.jalu.injector.InjectorBuilder;
|
||||
import com.github.authme.configme.properties.Property;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import fr.xephi.authme.security.HashAlgorithm;
|
||||
import fr.xephi.authme.security.crypts.EncryptionMethod;
|
||||
import fr.xephi.authme.security.crypts.HexSaltedMethod;
|
||||
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
|
||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Gathers information on {@link EncryptionMethod} implementations based on
|
||||
* the annotations in {@link fr.xephi.authme.security.crypts.description}.
|
||||
*/
|
||||
public class EncryptionMethodInfoGatherer {
|
||||
|
||||
private final static Set<Class<? extends Annotation>> RELEVANT_ANNOTATIONS =
|
||||
ImmutableSet.of(HasSalt.class, Recommendation.class, AsciiRestricted.class);
|
||||
|
||||
private static Injector injector = createInitializer();
|
||||
|
||||
private Map<HashAlgorithm, MethodDescription> descriptions;
|
||||
|
||||
public EncryptionMethodInfoGatherer() {
|
||||
descriptions = new LinkedHashMap<>();
|
||||
constructDescriptions();
|
||||
}
|
||||
|
||||
public Map<HashAlgorithm, MethodDescription> getDescriptions() {
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
private void constructDescriptions() {
|
||||
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
|
||||
if (!HashAlgorithm.CUSTOM.equals(algorithm) && !algorithm.getClazz().isAnnotationPresent(Deprecated.class)) {
|
||||
MethodDescription description = createDescription(algorithm);
|
||||
descriptions.put(algorithm, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodDescription createDescription(HashAlgorithm algorithm) {
|
||||
Class<? extends EncryptionMethod> clazz = algorithm.getClazz();
|
||||
EncryptionMethod method = injector.newInstance(clazz);
|
||||
if (method == null) {
|
||||
throw new NullPointerException("Method for '" + algorithm + "' is null");
|
||||
}
|
||||
MethodDescription description = new MethodDescription(clazz);
|
||||
description.setHashLength(method.computeHash("test", "user").getHash().length());
|
||||
description.setHasSeparateSalt(method.hasSeparateSalt());
|
||||
|
||||
Map<Class<?>, Annotation> annotationMap = gatherAnnotations(clazz);
|
||||
if (annotationMap.containsKey(HasSalt.class)) {
|
||||
setSaltInformation(description, returnTyped(annotationMap, HasSalt.class), method);
|
||||
}
|
||||
if (annotationMap.containsKey(Recommendation.class)) {
|
||||
description.setUsage(returnTyped(annotationMap, Recommendation.class).value());
|
||||
}
|
||||
if (annotationMap.containsKey(AsciiRestricted.class)) {
|
||||
description.setAsciiRestricted(true);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
private static Map<Class<?>, Annotation> gatherAnnotations(Class<?> methodClass) {
|
||||
// Note ljacqu 20151231: The map could be Map<Class<? extends Annotation>, Annotation> and it has the constraint
|
||||
// that for a key Class<T>, the value is of type T. We write a simple "Class<?>" for brevity.
|
||||
Map<Class<?>, Annotation> collection = new HashMap<>();
|
||||
Class<?> currentMethodClass = methodClass;
|
||||
while (currentMethodClass != null) {
|
||||
getRelevantAnnotations(currentMethodClass, collection);
|
||||
currentMethodClass = getSuperClass(currentMethodClass);
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
// Parameters could be Class<? extends EncryptionMethod>; Map<Class<? extends Annotation>, Annotation>
|
||||
// but the constraint doesn't have any technical relevance, so just clutters the code
|
||||
private static void getRelevantAnnotations(Class<?> methodClass, Map<Class<?>, Annotation> collection) {
|
||||
for (Annotation annotation : methodClass.getAnnotations()) {
|
||||
if (RELEVANT_ANNOTATIONS.contains(annotation.annotationType())
|
||||
&& !collection.containsKey(annotation.annotationType())) {
|
||||
collection.put(annotation.annotationType(), annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the super class of the given encryption method if it is also of EncryptionMethod type.
|
||||
* (Anything beyond EncryptionMethod is not of interest.)
|
||||
*/
|
||||
private static Class<?> getSuperClass(Class<?> methodClass) {
|
||||
Class<?> zuper = methodClass.getSuperclass();
|
||||
if (EncryptionMethod.class.isAssignableFrom(zuper)) {
|
||||
return zuper;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the salt information for the given encryption method and the found {@link HasSalt} annotation.
|
||||
* Also gets the salt length from {@link HexSaltedMethod#getSaltLength()} for such instances.
|
||||
*
|
||||
* @param description The description to update
|
||||
* @param hasSalt The associated HasSalt annotation
|
||||
* @param method The encryption method
|
||||
*/
|
||||
private static void setSaltInformation(MethodDescription description, HasSalt hasSalt, EncryptionMethod method) {
|
||||
description.setSaltType(hasSalt.value());
|
||||
if (hasSalt.length() != 0) {
|
||||
description.setSaltLength(hasSalt.length());
|
||||
} else if (method instanceof HexSaltedMethod) {
|
||||
int saltLength = ((HexSaltedMethod) method).getSaltLength();
|
||||
description.setSaltLength(saltLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience method for retrieving an annotation in a typed fashion.
|
||||
// We know implicitly that the key of the map always corresponds to the type of the value
|
||||
private static <T> T returnTyped(Map<Class<?>, Annotation> map, Class<T> key) {
|
||||
return key.cast(map.get(key));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Injector createInitializer() {
|
||||
Settings settings = mock(Settings.class);
|
||||
// Return the default value for any property
|
||||
when(settings.getProperty(any(Property.class))).thenAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
Property<?> property = (Property<?>) invocation.getArguments()[0];
|
||||
return property.getDefaultValue();
|
||||
}
|
||||
});
|
||||
|
||||
// By passing some bogus "package" to the constructor, the injector will throw if it needs to
|
||||
// instantiate any dependency other than what we provide.
|
||||
Injector injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme.security.crypts").create();
|
||||
injector.register(Settings.class, settings);
|
||||
return injector;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package tools.docs.hashmethods;
|
||||
|
||||
import fr.xephi.authme.security.HashAlgorithm;
|
||||
import tools.utils.AutoToolTask;
|
||||
import tools.utils.FileUtils;
|
||||
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.
|
||||
*
|
||||
* @see fr.xephi.authme.security.HashAlgorithm
|
||||
*/
|
||||
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
|
||||
EncryptionMethodInfoGatherer infoGatherer = new EncryptionMethodInfoGatherer();
|
||||
Map<HashAlgorithm, MethodDescription> descriptions = infoGatherer.getDescriptions();
|
||||
final NestedTagValue methodRows = constructMethodRows(descriptions);
|
||||
|
||||
// Write to the docs file
|
||||
TagValueHolder tags = TagValueHolder.create().put("algorithms", methodRows);
|
||||
FileUtils.generateFileFromTemplate(CUR_FOLDER + "hash_algorithms.tpl.md", OUTPUT_FILE, tags);
|
||||
}
|
||||
|
||||
private static NestedTagValue constructMethodRows(Map<HashAlgorithm, MethodDescription> descriptions) {
|
||||
NestedTagValue methodTags = new NestedTagValue();
|
||||
for (Map.Entry<HashAlgorithm, MethodDescription> entry : descriptions.entrySet()) {
|
||||
MethodDescription description = entry.getValue();
|
||||
TagValueHolder tags = TagValueHolder.create()
|
||||
.put("name", asString(entry.getKey()))
|
||||
.put("recommendation", asString(description.getUsage()))
|
||||
.put("hash_length", asString(description.getHashLength()))
|
||||
.put("ascii_restricted", asString(description.isAsciiRestricted()))
|
||||
.put("salt_type", asString(description.getSaltType()))
|
||||
.put("salt_length", asString(description.getSaltLength()))
|
||||
.put("separate_salt", asString(description.hasSeparateSalt()));
|
||||
methodTags.add(tags);
|
||||
}
|
||||
return methodTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "describeHashAlgos";
|
||||
}
|
||||
|
||||
// ----
|
||||
// String representations
|
||||
// ----
|
||||
private static String asString(boolean value) {
|
||||
return value ? "Y" : "";
|
||||
}
|
||||
|
||||
private static String asString(int value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
private static String asString(Integer value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
private static String asString(HashAlgorithm value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> String asString(E value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
// Get the enum name and replace something like "DO_NOT_USE" to "Do not use"
|
||||
String enumName = value.toString().replace("_", " ");
|
||||
return enumName.length() > 2
|
||||
? enumName.substring(0, 1) + enumName.substring(1).toLowerCase()
|
||||
: enumName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package tools.docs.hashmethods;
|
||||
|
||||
import fr.xephi.authme.security.crypts.EncryptionMethod;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
/**
|
||||
* Description of a {@link EncryptionMethod}.
|
||||
*/
|
||||
public class MethodDescription {
|
||||
|
||||
/** The implementation class the description belongs to. */
|
||||
private final Class<? extends EncryptionMethod> method;
|
||||
/** The type of the salt that is used. */
|
||||
private SaltType saltType;
|
||||
/** The length of the salt for SaltType.TEXT salts. */
|
||||
private Integer saltLength;
|
||||
/** The usage recommendation. */
|
||||
private Usage usage;
|
||||
/** Whether or not the encryption method is restricted to ASCII characters for proper functioning. */
|
||||
private boolean asciiRestricted;
|
||||
/** Whether or not the encryption method requires its salt stored separately. */
|
||||
private boolean hasSeparateSalt;
|
||||
/** The length of the hash output, based on a test hash (i.e. assumes same length for all hashes.) */
|
||||
private int hashLength;
|
||||
|
||||
public MethodDescription(Class<? extends EncryptionMethod> method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
|
||||
// Trivial getters and setters
|
||||
public Class<? extends EncryptionMethod> getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public SaltType getSaltType() {
|
||||
return saltType;
|
||||
}
|
||||
|
||||
public void setSaltType(SaltType saltType) {
|
||||
this.saltType = saltType;
|
||||
}
|
||||
|
||||
public Integer getSaltLength() {
|
||||
return saltLength;
|
||||
}
|
||||
|
||||
public void setSaltLength(int saltLength) {
|
||||
this.saltLength = saltLength;
|
||||
}
|
||||
|
||||
public Usage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public void setUsage(Usage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public boolean isAsciiRestricted() {
|
||||
return asciiRestricted;
|
||||
}
|
||||
|
||||
public void setAsciiRestricted(boolean asciiRestricted) {
|
||||
this.asciiRestricted = asciiRestricted;
|
||||
}
|
||||
|
||||
public boolean hasSeparateSalt() {
|
||||
return hasSeparateSalt;
|
||||
}
|
||||
|
||||
public void setHasSeparateSalt(boolean hasSeparateSalt) {
|
||||
this.hasSeparateSalt = hasSeparateSalt;
|
||||
}
|
||||
|
||||
public int getHashLength() {
|
||||
return hashLength;
|
||||
}
|
||||
|
||||
public void setHashLength(int hashLength) {
|
||||
this.hashLength = hashLength;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<!-- {gen_warning} -->
|
||||
<!-- File auto-generated on {gen_date}. See hashmethods/hash_algorithms.tpl.md -->
|
||||
|
||||
## Hash Algorithms
|
||||
AuthMe supports the following hash algorithms for storing your passwords safely.
|
||||
|
||||
|
||||
Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate?
|
||||
--------- | -------------- | ----------- | ----- | --- | --------- | ------ | ---------
|
||||
[#algorithms]
|
||||
{name} | {recommendation} | {hash_length} | {ascii_restricted} | | {salt_type} | {salt_length} | {separate_salt}
|
||||
[/#algorithms]CUSTOM | | | | | | | |
|
||||
|
||||
<!-- {gen_warning} -->
|
||||
|
||||
### Columns
|
||||
#### Algorithm
|
||||
The algorithm is the hashing algorithm used to store passwords with. Default is SHA256 and is recommended.
|
||||
You can change the hashing algorithm in the config.yml: under `security`, locate `passwordHash`.
|
||||
|
||||
#### Recommendation
|
||||
The recommendation lists our usage recommendation in terms of how secure it is (not how _well_ the algorithm works!).
|
||||
- Recommended: The hash algorithm appears to be cryptographically secure and is one we recommend.
|
||||
- Acceptable: There are safer algorithms that can be chosen but using the algorithm is generally OK.
|
||||
- Do not use: Hash algorithm isn't sufficiently secure. Use only if required to hook into another system.
|
||||
- Does not work: The algorithm does not work properly; do not use.
|
||||
|
||||
#### Hash Length
|
||||
The length of the hashes the algorithm produces. Note that the hash length is not (primarily) indicative of
|
||||
whether an algorithm is secure or not.
|
||||
|
||||
#### ASCII
|
||||
If denoted with a **y**, means that the algorithm is restricted to ASCII characters only, i.e. it will simply ignore
|
||||
"special characters" such as `ÿ` or `Â`. Note that we do not recommend the use of "special characters" in passwords.
|
||||
|
||||
#### Salt Columns
|
||||
Before hashing, a _salt_ may be appended to the password to make the hash more secure. The following columns describe
|
||||
the salt the algorithm uses.
|
||||
<!-- {gen_warning} -->
|
||||
|
||||
##### Salt Type
|
||||
We do not recommend the usage
|
||||
of any algorithm that doesn't use a randomly generated text as salt. This "salt type" column indicates what type of
|
||||
salt the algorithm uses:
|
||||
- Text: randomly generated text (see also the following column, "Length")
|
||||
- Username: the salt is constructed from the username (bad)
|
||||
- None: the algorithm uses no salt (bad)
|
||||
|
||||
##### Length
|
||||
If applicable (salt type is "Text"), indicates the length of the generated salt. The longer the better.
|
||||
If this column is empty when the salt type is "Text", it typically means the salt length can be defined in config.yml.
|
||||
|
||||
##### Separate
|
||||
If denoted with a **y**, it means that the salt is stored in a separate column in the database. This is neither good
|
||||
or bad.
|
||||
|
||||
{gen_footer}
|
||||
@@ -0,0 +1,88 @@
|
||||
package tools.docs.permissions;
|
||||
|
||||
import fr.xephi.authme.ClassCollector;
|
||||
import fr.xephi.authme.permission.PermissionNode;
|
||||
import tools.utils.FileUtils;
|
||||
import tools.utils.ToolsConstants;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Gatherer to generate up-to-date lists of the AuthMe permission nodes.
|
||||
*/
|
||||
public class PermissionNodesGatherer {
|
||||
|
||||
/**
|
||||
* Regular expression that should match the JavaDoc comment above an enum, <i>including</i>
|
||||
* the name of the enum value. The first group (i.e. {@code \\1}) should be the JavaDoc description;
|
||||
* the second group should contain the enum value.
|
||||
*/
|
||||
private static final Pattern JAVADOC_WITH_ENUM_PATTERN = Pattern.compile(
|
||||
"/\\*\\*\\s+\\*" // Match starting '/**' and the '*' on the next line
|
||||
+ "(.*?)\\s+\\*/" // Capture everything until we encounter '*/'
|
||||
+ "\\s+([A-Z_]+)\\("); // Match the enum name (e.g. 'LOGIN'), until before the first '('
|
||||
|
||||
/**
|
||||
* Return a sorted collection of all permission nodes, including its JavaDoc description.
|
||||
*
|
||||
* @return Ordered map whose keys are the permission nodes and the values the associated JavaDoc
|
||||
*/
|
||||
public <T extends Enum<T> & PermissionNode> Map<String, String> gatherNodesWithJavaDoc() {
|
||||
Map<String, String> result = new TreeMap<>();
|
||||
result.put("authme.admin.*", "Give access to all admin commands.");
|
||||
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));
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T extends Enum<T> & PermissionNode> void addDescriptionsForClass(Class<T> clazz,
|
||||
Map<String, String> descriptions) {
|
||||
String classSource = getSourceForClass(clazz);
|
||||
Map<String, String> sourceDescriptions = extractJavaDocFromSource(classSource);
|
||||
|
||||
for (T perm : EnumSet.allOf(clazz)) {
|
||||
String description = sourceDescriptions.get(perm.name());
|
||||
if (description == null) {
|
||||
System.out.println("Note: Could not retrieve description for "
|
||||
+ clazz.getSimpleName() + "#" + perm.name());
|
||||
description = "";
|
||||
}
|
||||
descriptions.put(perm.getNode(), description.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> extractJavaDocFromSource(String source) {
|
||||
Map<String, String> allMatches = new HashMap<>();
|
||||
Matcher matcher = JAVADOC_WITH_ENUM_PATTERN.matcher(source);
|
||||
while (matcher.find()) {
|
||||
String description = matcher.group(1);
|
||||
String enumValue = matcher.group(2);
|
||||
allMatches.put(enumValue, description);
|
||||
}
|
||||
return allMatches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Java source code for the given implementation of {@link PermissionNode}.
|
||||
*
|
||||
* @param clazz The clazz to the get the source for
|
||||
* @param <T> The concrete type
|
||||
* @return Source code of the file
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package tools.docs.permissions;
|
||||
|
||||
import tools.utils.AutoToolTask;
|
||||
import tools.utils.FileUtils;
|
||||
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
|
||||
* for writing it to a file if desired.
|
||||
*/
|
||||
public class PermissionsListWriter implements AutoToolTask {
|
||||
|
||||
private static final String TEMPLATE_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "docs/permissions/permission_nodes.tpl.md";
|
||||
private static final String PERMISSIONS_OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "permission_nodes.md";
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
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);
|
||||
System.out.println("Wrote to '" + PERMISSIONS_OUTPUT_FILE + "'");
|
||||
System.out.println("Before committing, please verify the output!");
|
||||
}
|
||||
|
||||
private static NestedTagValue generatePermissionsList() {
|
||||
PermissionNodesGatherer gatherer = new PermissionNodesGatherer();
|
||||
Map<String, String> permissions = gatherer.gatherNodesWithJavaDoc();
|
||||
NestedTagValue permissionTags = new NestedTagValue();
|
||||
for (Map.Entry<String, String> entry : permissions.entrySet()) {
|
||||
permissionTags.add(TagValueHolder.create()
|
||||
.put("node", entry.getKey())
|
||||
.put("description", entry.getValue()));
|
||||
}
|
||||
return permissionTags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- {gen_warning} -->
|
||||
<!-- File auto-generated on {gen_date}. See permissions/permission_nodes.tpl.md -->
|
||||
|
||||
## AuthMe Permission Nodes
|
||||
The following are the permission nodes that are currently supported by the latest dev builds.
|
||||
|
||||
[#nodes]
|
||||
- **{node}** – {description}
|
||||
[/#nodes]
|
||||
|
||||
{gen_footer}
|
||||
Reference in New Issue
Block a user