Internal commands and command parser.

keep-around/d31701866686f66088b78de2e29736ae36e55a68
Pingex aka Raphaël 9 years ago
parent 60c12e3290
commit eb8ec72406

@ -1,5 +1,7 @@
package net.pingex.dcf;
import net.pingex.dcf.commands.CommandRegistry;
import net.pingex.dcf.commands.InternalCommands;
import net.pingex.dcf.core.Configuration;
import net.pingex.dcf.core.GatewayConnectionsManager;
import net.pingex.dcf.modularity.PluginLoader;
@ -40,6 +42,9 @@ public class DiscordCommandableFramework
else builder.withLogin(Configuration.CONNECTION_USERNAME, Configuration.CONNECTION_PASSWORD);
GatewayConnectionsManager.registerConnection(builder);
// Register internal commands
new InternalCommands().getCommands().forEach(CommandRegistry::registerCommand);
// Run plugins
PluginLoader.getInstance().bulkRunPlugins();
}

@ -0,0 +1,152 @@
package net.pingex.dcf.commands;
import net.pingex.dcf.core.Configuration;
import net.pingex.dcf.util.ArgumentParser;
import net.pingex.dcf.util.DiscordInteractionsUtil;
import org.apache.commons.lang3.StringUtils;
import sx.blah.discord.handle.impl.events.MessageReceivedEvent;
import java.util.*;
/**
* Internal commands of DCF.
*/
public class InternalCommands implements IWithCommands
{
@Override
public Set<Command> getCommands()
{
return new HashSet<>(Arrays.asList(ListCommand.INSTANCE, UsageCommand.INSTANCE));
}
/**
* This command list all available commands on DCF.
*/
private static class ListCommand extends Command
{
private static final String NAME = "internal:list";
private static final List<String> ALIASES = Arrays.asList("list", "help");
private static final String DESCRIPTION = "List all available commands.";
private static final boolean IS_ENABLED = true;
private static final String USAGE = Configuration.COMMAND_PREFIX + NAME + " <page>";
/**
* How many commands should be displayed on each page
*/
public static final int COMMANDS_PER_PAGE = 5;
static final ListCommand INSTANCE = new ListCommand();
private ListCommand()
{
super(NAME, ALIASES, DESCRIPTION, IS_ENABLED, USAGE);
}
@Override
public void execute(MessageReceivedEvent event, List<String> arguments)
{
// Parameters
Set<Command> bank = CommandRegistry.getRegistry();
int amountPages = (int) Math.ceil(bank.size()/(double)COMMANDS_PER_PAGE);
int requestedPage;
int longestCommand = bank.stream().max((o1, o2) -> Integer.compare(o1.getName().length(), o2.getName().length())).get().getName().length();
int longestDesc = bank.stream().max((o1, o2) -> Integer.compare(o1.getDescription().length(), o2.getDescription().length())).get().getDescription().length();
// Parsing
try
{
List<Object> output = ArgumentParser.parseAll(Collections.singletonList(Integer.class), arguments);
requestedPage = output.get(0) != null ? (int) output.get(0) : 1;
}
catch(ArgumentParser.ParserException e)
{
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), e.getMessage());
return;
}
// Checks
if(requestedPage <= 0 || requestedPage > amountPages)
{
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Requested page is invalid. Number of available pages: " + amountPages);
return;
}
StringBuilder output = new StringBuilder
("**List of commands available for " + Configuration.BOT_NAME + "** (page " + requestedPage + "/" + amountPages + ")\n");
output.append("Use `").append(UsageCommand.USAGE).append("`")
.append(" to get details and usage about any command.\n");
output.append("```");
int pos = 1;
for(Command i : bank)
{
if(pos > (requestedPage-1)*COMMANDS_PER_PAGE && pos <= requestedPage*COMMANDS_PER_PAGE)
{
output.append("+ ").append(StringUtils.rightPad(i.getName(), longestCommand)); // Name
output.append(" ").append(StringUtils.rightPad(i.getDescription(), longestDesc)); // Desc
if(i.getAliases().size() > 0)
output.append(" ").append("(aliases: ").append(StringUtils.join(i.getAliases(), ", ")).append(")"); // Aliases
output.append("\n");
}
pos++;
}
output.append("```");
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), output.toString());
}
}
/**
* Gives the usage of a command.
*/
private static class UsageCommand extends Command
{
private static final String NAME = "internal:usage";
private static final List<String> ALIASES = Collections.singletonList("usage");
private static final String DESCRIPTION = "Gives the usage of a command.";
private static final boolean IS_ENABLED = true;
private static final String USAGE = Configuration.COMMAND_PREFIX + NAME + " <command>";
static final UsageCommand INSTANCE = new UsageCommand();
private UsageCommand()
{
super(NAME, ALIASES, DESCRIPTION, IS_ENABLED, USAGE);
}
@Override
public void execute(MessageReceivedEvent event, List<String> arguments) throws Throwable
{
// Checks
if(arguments.size() != 1) // Arg check
{
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Invalid argument.");
return;
}
Optional<Command> uncheckedTarget = CommandRegistry.getCommandOrAliasByName(arguments.get(0));
if(!uncheckedTarget.isPresent()) // Command existence
{
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Target command not found.");
return;
}
// Print everything
Command target = uncheckedTarget.get();
StringBuilder output = new StringBuilder();
output.append("**Details and usage for command `")
.append(Configuration.COMMAND_PREFIX).append(target.getName())
.append("`**\n")
.append("```");
output.append("Description: ").append(target.getDescription()).append("\n");
if(target.getAliases().size() > 0)
output.append("Aliases: ").append(StringUtils.join(target.getAliases(), ", ")).append("\n");
output.append("Usage: ").append(target.getUsage()).append("\n");
output.append("Enabled: ").append(target.isEnabled() ? "Yes" : "No").append("\n");
output.append("```");
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), output.toString());
}
}
}

@ -0,0 +1,105 @@
package net.pingex.dcf.util;
import org.apache.commons.lang3.ClassUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Util class used to parse a String into primitive types.
*/
public class ArgumentParser
{
/**
* Parses a String into a primitive variable.
* Choice between boolean, byte, short, int, long, float, double, String and their respective wrapper types.
* @param target Target primitive
* @param value Input value
* @return `null` if the value is null or the string content is null, or the parsed value.
* @throws IllegalArgumentException If the target primitive/class isn't part of the previous list.
* @throws NumberFormatException if the cast failed.
*/
public static Object parse(Class<?> target, String value) throws IllegalArgumentException
{
if(value == null || value.equals("null") ) return null;
if(Boolean.class == target || Boolean.TYPE == target) return Boolean.parseBoolean(value);
if(Byte.class == target || Byte.TYPE == target) return Byte.parseByte(value);
if(Short.class == target || Short.TYPE == target) return Short.parseShort(value);
if(Integer.class == target || Integer.TYPE == target) return Integer.parseInt(value);
if(Long.class == target || Long.TYPE == target) return Long.parseLong(value);
if(Float.class == target || Float.TYPE == target) return Float.parseFloat(value);
if(Double.class == target || Double.TYPE == target) return Double.parseDouble(value);
if(String.class == target) return value;
throw new IllegalArgumentException("Unknown target type");
}
/**
* Parse the input arguments to a list of argument of various types.
* Use a primitive type for an argument that is required.
* Use a wrapper type to an argument that is optional, thereby the said argument can be {@code null}.
* Any extra argument not specified in {@code targetTypes} are ignored and will not appear in the output list.
*
* @param targetTypes A list of types that the {@code arguments} will be checked against.
* @param arguments Input arguments to check
* @return The parsed arguments whose types matches {@code targetTypes}
* @throws ParserException if the parsing failed.
*/
public static List<Object> parseAll(List<Class<?>> targetTypes, List<String> arguments) throws ParserException
{
if(targetTypes.size() == 0) throw new ParserException("Nothing to parse.");
List<Object> outputList = new ArrayList<>();
for(int i = 0; i < targetTypes.size(); i++)
{
try
{
Object parsingOutput = parse(targetTypes.get(i), arguments.get(i));
if(parsingOutput == null)
{
if(ClassUtils.isPrimitiveWrapper(targetTypes.get(i)) || targetTypes.get(i) == String.class) outputList.add(null); // Allows null
else throw new ParserException("Invalid arguments."); // Disallows null
}
else outputList.add(parsingOutput);
}
catch(IndexOutOfBoundsException e) // Not enough arguments supplied
{
if(ClassUtils.isPrimitiveWrapper(targetTypes.get(i)) || targetTypes.get(i) == String.class) outputList.add(null); // Allows null
else throw new ParserException("Not enough arguments supplied."); // Disallows null
}
catch(IllegalArgumentException e)
{
if(e instanceof NumberFormatException) throw new ParserException("Invalid arguments."); // Cannot be casted because the user fucked up
else throw new ParserException("Parsing error, report to dev.", e); // Cannot be casted because the dev fucked up
}
}
return outputList;
}
/**
* Thrown whenever any parse operation failed.
*/
public static class ParserException extends RuntimeException
{
public ParserException(String message, Throwable cause)
{
super(message, cause);
}
public ParserException(Throwable cause)
{
super(cause);
}
public ParserException()
{
super();
}
public ParserException(String message)
{
super(message);
}
}
}