text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to indicate that a specific parameter is optional.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Optional {
    /**
     * The value will be used if the argument is not provided.
     *

     * Special values:
     *

     *
null => null

     *
self (for player types) => the sender

     *

     *

     * All other values will be parsed using the registered argument providers.
     */
    String value() default "null";

    /**
     * This method indicates whether the argument providers should return null instead of an exception if parsing fails.
     *

     *
Example:
     *
Command declaration: statsCommand(@Sender Player sender, @Name("player") @Optional Player target)
     *
Executed commands:
     *

     *
/stats -> null gets passed because no argument was provided

     *
/stats OnlinePlayer -> OnlinePlayer gets passed

     *
/stats OfflinePlayer -> since the player is not online an exception would be thrown (printing an error to the user), but if ignoreFailedArgumentParse is true null gets returned instead

     *

     */
    boolean ignoreFailedArgumentParse() default false;
}


text:
package me.vaperion.blade.exception;

public class BladeExitMessage extends RuntimeException {

    public BladeExitMessage() {
        this("Command execution failed.");
    }

    public BladeExitMessage(String message) {
        super(message);
    }
}

repo_name: vaperion/blade

repo_language: Java

mime_type: text/x-java


text:
package me.vaperion.blade.exception;

public class BladeUsageMessage extends BladeExitMessage {
    public BladeUsageMessage() {
        super("");
    }
}


text:
package me.vaperion.blade.context;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.annotation.argument.Optional;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.command.Parameter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;

@RequiredArgsConstructor
public final class Context {

    private static final Optional PARSE_OPTIONAL_ARG = new Optional() {
        @Override
        public String value() {
            return "null"; // This is not used inside argument providers, so we can set it statically here.
        }

        @Override
        public boolean ignoreFailedArgumentParse() {
            return false;
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return Optional.class;
        }
    };

    private final Blade blade;

    private final WrappedSender<?> sender;
    private final String alias;
    private final String[] arguments;

    public void reply(@NotNull String message) {
        sender.sendMessage(message);
    }

    public void reply(@NotNull String... message) {
        sender.sendMessage(message);
    }

    @NotNull
    public String[] arguments() {
        return arguments;
    }

    @Nullable
    public String argument(int index) {
        if (index < 0 || index >= arguments.length) return null;
        return arguments[index];
    }

    @Nullable
    public <T> T parseArgument(int index, Class<T> argumentClass) {
        return parseArgument(index, argumentClass, "");
    }

    @Nullable
    public <T> T parseArgument(int index, Class<T> argumentClass, String defaultValue) {
        ArgumentProvider<T> provider = blade.getResolver().recursiveResolveProvider(argumentClass, Collections.emptyList());

        if (provider == null)
            throw new IllegalArgumentException("No provider found for " + argumentClass.getName());

        return parseArgument(index, provider, defaultValue);
    }

    @Nullable
    public <T> T parseArgument(int index, ArgumentProvider<T> provider) {
        return parseArgument(index, provider, "");
    }

    @Nullable
    public <T> T parseArgument(int index, ArgumentProvider<T> provider, String defaultValue) {
        Argument arg = new Argument(new Parameter(
              /*name*/ "argument " + (index + 1),
              /*type*/ (Class<?>) ((ParameterizedType) provider.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0],
              /*data*/ Collections.emptyList(),
              /*optional annotation*/ PARSE_OPTIONAL_ARG,
              /*range annotation*/ null,
              /*completer annotation*/null,
              /*text?*/ false,
              /*element*/ null
        ));

        String provided = argument(index);
        if (provided == null) {
            arg.setType(Argument.Type.OPTIONAL);
            arg.setString(defaultValue);
        } else {
            arg.setType(Argument.Type.PROVIDED);
            arg.setString(provided);
        }

        return provider.provide(this, arg);
    }

    @NotNull
    public String alias() {
        return alias;
    }

    @NotNull
    public WrappedSender<?> sender() {
        return sender;
    }

    @NotNull
    public Blade blade() {
        return blade;
    }

}


text:
package me.vaperion.blade.context;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface WrappedSender<T> {
    @NotNull T getSender();

    @NotNull String getName();

    boolean hasPermission(@NotNull String permission);

    void sendMessage(@NotNull String message);

    void sendMessage(@NotNull String... messages);

    @Nullable <S> S parseAs(@NotNull Class<S> clazz);
}


text:
package me.vaperion.blade.command;

import me.vaperion.blade.context.Context;
import org.jetbrains.annotations.NotNull;

public interface UsageMessage {
    void sendTo(@NotNull Context context);

    @NotNull String toString();
}


text:
package me.vaperion.blade.command;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.annotation.argument.Completer;
import me.vaperion.blade.annotation.argument.Flag;
import me.vaperion.blade.annotation.argument.Optional;
import me.vaperion.blade.annotation.argument.Range;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.AnnotatedElement;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Getter
@RequiredArgsConstructor
public class Parameter {

    private static final Map<Class<? extends ArgumentProvider<?>>, ArgumentProvider<?>> COMPLETER_CACHE = new ConcurrentHashMap<>();

    final String name;
    final Class<?> type;
    final List<String> data;

    final Optional optional;
    final Range range;
    final Completer completer;

    final boolean text;
    final AnnotatedElement element;

    public boolean isOptional() {
        return optional != null;
    }

    public boolean hasRange() {
        return range != null;
    }

    public boolean hasCustomCompleter() {
        return completer != null;
    }

    @Nullable
    public ArgumentProvider<?> getCustomCompleter() {
        if (!hasCustomCompleter()) return null;
        try {
            return COMPLETER_CACHE.computeIfAbsent(completer.value(), c -> {
                try {
                    return c.newInstance();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            });
        } catch (Exception e) {
            if (e.getCause() != null) e.getCause().printStackTrace();
            else e.printStackTrace();
            throw new BladeExitMessage("An exception was thrown while attempting to load the custom tab completer.");
        }
    }

    @Nullable
    public String getDefault() {
        return isOptional() ? optional.value() : null;
    }

    public boolean ignoreFailedArgumentParse() {
        return isOptional() && optional.ignoreFailedArgumentParse();
    }

    public boolean defaultsToNull() {
        return isOptional() && "null".equals(optional.value());
    }

    public static final class CommandParameter extends Parameter {
        public CommandParameter(String name, Class<?> type, List<String> data, Optional optional, Range range, Completer completer, boolean text, AnnotatedElement element) {
            super(name, type, data, optional, range, completer, text, element);
        }
    }

    public static final class FlagParameter extends Parameter {
        @Getter private final Flag flag;

        public FlagParameter(String name, Class<?> type, Optional optional, AnnotatedElement element, Flag flag) {
            super(name, type, Collections.emptyList(), optional, null, null, false, element);
            this.flag = flag;
        }

        public boolean isBooleanFlag() {
            return this.type == boolean.class;
        }

        @Nullable
        public String extractFrom(Map<Character, String> flagMap) {
            if (!flagMap.containsKey(flag.value())) {
                if (this.optional != null) return this.optional.value();
                return isBooleanFlag() ? "false" : null;
            }

            return flagMap.get(flag.value());
        }
    }

}


text:
package me.vaperion.blade.command;

import lombok.Getter;
import me.vaperion.blade.Blade;
import me.vaperion.blade.annotation.argument.*;
import me.vaperion.blade.annotation.command.*;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.command.Parameter.CommandParameter;
import me.vaperion.blade.command.Parameter.FlagParameter;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.context.WrappedSender;
import me.vaperion.blade.util.ClassUtil;
import me.vaperion.blade.util.LoadedValue;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static me.vaperion.blade.util.Preconditions.checkNotEmpty;
import static me.vaperion.blade.util.Preconditions.runOrDefault;

@Getter
public final class Command {

    private final Blade blade;

    private final Object instance;
    private final Method method;
    private final String[] aliases, baseCommands;
    private final String description, usageAlias, customUsage, extraUsageData;
    private final String permission, permissionMessage;
    private final boolean async, quoted, hidden;

    private final boolean hasSenderParameter, contextBased, wrappedSenderBased;
    private final Class<?> senderType;

    private final List<Parameter> parameters = new ArrayList<>();
    private final List<ArgumentProvider<?>> providers = new ArrayList<>(), parameterProviders = new ArrayList<>(), flagProviders = new ArrayList<>();

    private final LoadedValue<UsageMessage> usageMessage = new LoadedValue<>(), helpMessage = new LoadedValue<>();

    public Command(Blade blade, Object instance, Method method) {
        this.blade = blade;

        this.instance = instance;
        this.method = method;

        this.aliases = method.getAnnotation(me.vaperion.blade.annotation.command.Command.class).value();
        this.description = runOrDefault(method.getAnnotation(Description.class), "", Description::value);
        this.async = runOrDefault(method.getAnnotation(Async.class), false, $ -> true);
        this.hidden = runOrDefault(method.getAnnotation(Hidden.class), false, $ -> true);
        this.usageAlias = runOrDefault(method.getAnnotation(UsageAlias.class), this.aliases[0], UsageAlias::value);
        this.customUsage = runOrDefault(method.getAnnotation(Usage.class), "", Usage::value);
        this.extraUsageData = runOrDefault(method.getAnnotation(ExtraUsage.class), "", ExtraUsage::value);

        this.baseCommands = Arrays.stream(aliases)
              .map(String::toLowerCase)
              .map(s -> s.split(" ")[0])
              .distinct().toArray(String[]::new);

        Permission permission = method.getAnnotation(Permission.class);
        this.permission = permission != null ? permission.value() : "";
        this.permissionMessage = checkNotEmpty(permission != null ? permission.message() : "", blade.getConfiguration().getDefaultPermissionMessage());

        this.quoted = method.isAnnotationPresent(ParseQuotes.class);

        this.hasSenderParameter = method.getParameterCount() > 0 && method.getParameters()[0].isAnnotationPresent(Sender.class);
        this.senderType = hasSenderParameter ? method.getParameters()[0].getType() : null;
        this.contextBased = method.getParameterCount() == 1 && method.getParameterTypes()[0] == Context.class;
        this.wrappedSenderBased = method.getParameterCount() == 1 && method.getParameterTypes()[0] == WrappedSender.class;

        method.setAccessible(true);

        int i = 0;
        for (java.lang.reflect.Parameter parameter : method.getParameters()) {
            if (i == 0 && hasSenderParameter) {
                i++;
                continue;
            }

            Class<?> type = ClassUtil.getGenericOrRawType(parameter);

            String parameterName = parameter.isAnnotationPresent(Name.class) ? parameter.getAnnotation(Name.class).value() : parameter.getName();
            String[] parameterData = parameter.isAnnotationPresent(Data.class) ? parameter.getAnnotation(Data.class).value() : null;
            Parameter bladeParameter;

            if (parameter.isAnnotationPresent(Flag.class)) {
                Flag flag = parameter.getAnnotation(Flag.class);
                bladeParameter = new FlagParameter(parameterName, type, parameter.getAnnotation(Optional.class), parameter, flag);
            } else {
                bladeParameter = new CommandParameter(parameterName, type,
                      parameterData == null ? Collections.emptyList() : Arrays.asList(parameterData), parameter.getAnnotation(Optional.class),
                      parameter.getAnnotation(Range.class), parameter.getAnnotation(Completer.class), parameter.isAnnotationPresent(Text.class), parameter);
            }

            ArgumentProvider<?> provider = blade.getResolver().recursiveResolveProvider(type, Arrays.asList(parameter.getAnnotations()));

            parameters.add(bladeParameter);
            providers.add(provider);

            if (bladeParameter instanceof FlagParameter)
                flagProviders.add(provider);
            else
                parameterProviders.add(provider);

            i++;
        }
    }

    @NotNull
    public List<CommandParameter> getCommandParameters() {
        return parameters.stream()
              .filter(CommandParameter.class::isInstance)
              .map(CommandParameter.class::cast)
              .collect(Collectors.toList());
    }

    @NotNull
    public List<FlagParameter> getFlagParameters() {
        return parameters.stream()
              .filter(FlagParameter.class::isInstance)
              .map(FlagParameter.class::cast)
              .collect(Collectors.toList());
    }

}


text:
package me.vaperion.blade.argument;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import me.vaperion.blade.command.Parameter;

import java.util.ArrayList;
import java.util.List;

@Getter
@RequiredArgsConstructor
public final class Argument {

    private final Parameter parameter;

    @Setter private Type type;
    @Setter private String string;
    private final List<String> data = new ArrayList<>();

    public enum Type {
        PROVIDED,
        OPTIONAL
    }

}


text:
package me.vaperion.blade.argument;

import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.List;

@FunctionalInterface
public interface ArgumentProvider<T> {
    @Nullable T provide(@NotNull Context context, @NotNull Argument argument) throws BladeExitMessage;

    @NotNull
    default List<String> suggest(@NotNull Context context, @NotNull Argument argument) throws BladeExitMessage {
        return Collections.emptyList();
    }
}


text:
package me.vaperion.blade.argument;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.util.List;

@SuppressWarnings({"rawtypes", "unchecked"})
@Getter
@RequiredArgsConstructor
public class Provider<T> {

    @NotNull
    @Contract("_, _, _ -> new")
    public static Provider<?> unsafe(@NotNull Class<?> type, @NotNull ArgumentProvider<?> provider, @NotNull List<Class<? extends Annotation>> requiredAnnotations) {
        return new Provider(type, provider, requiredAnnotations);
    }

    private final Class<T> type;
    private final ArgumentProvider<T> provider;
    private final List<Class<? extends Annotation>> requiredAnnotations;

    public boolean doAnnotationsMatch(@Nullable List<Class<? extends Annotation>> annotations) {
        if (annotations == null || requiredAnnotations == null) return false;
        if (annotations.size() != requiredAnnotations.size()) return false;
        return requiredAnnotations.containsAll(annotations);
    }

}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class StringArgument implements ArgumentProvider<String> {
    @Override
    public @Nullable String provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        if (arg.getString() == null) {
            if (arg.getParameter().ignoreFailedArgumentParse()) return null;
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid string.");
        }

        return arg.getString();
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.annotation.argument.Range;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.text.DecimalFormat;

public class FloatArgument implements ArgumentProvider<Float> {
    private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#.#");

    @Override
    public @Nullable Float provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        float input;
        try {
            input = Float.parseFloat(arg.getString());
        } catch (NumberFormatException e) {
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid number.");
        }

        if (arg.getParameter().hasRange()) {
            Range range = arg.getParameter().getRange();

            if (!Double.isNaN(range.min()) && input < range.min())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at least " + NUMBER_FORMAT.format(range.min()) + ".");
            else if (!Double.isNaN(range.max()) && input > range.max())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at most " + NUMBER_FORMAT.format(range.max()) + ".");
        }

        return input;
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.annotation.argument.Range;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.text.DecimalFormat;

public class IntArgument implements ArgumentProvider<Integer> {
    private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#.#");

    @Override
    public @Nullable Integer provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        int input;
        try {
            input = Integer.parseInt(arg.getString());
        } catch (NumberFormatException e) {
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid number.");
        }

        if (arg.getParameter().hasRange()) {
            Range range = arg.getParameter().getRange();

            if (!Double.isNaN(range.min()) && input < range.min())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at least " + NUMBER_FORMAT.format(range.min()) + ".");
            else if (!Double.isNaN(range.max()) && input > range.max())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at most " + NUMBER_FORMAT.format(range.max()) + ".");
        }

        return input;
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.annotation.argument.Range;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.text.DecimalFormat;

public class LongArgument implements ArgumentProvider<Long> {
    private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#.#");

    @Override
    public @Nullable Long provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        long input;
        try {
            input = Long.parseLong(arg.getString());
        } catch (NumberFormatException e) {
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid number.");
        }

        if (arg.getParameter().hasRange()) {
            Range range = arg.getParameter().getRange();

            if (!Double.isNaN(range.min()) && input < range.min())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at least " + NUMBER_FORMAT.format(range.min()) + ".");
            else if (!Double.isNaN(range.max()) && input > range.max())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at most " + NUMBER_FORMAT.format(range.max()) + ".");
        }

        return input;
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@SuppressWarnings({"rawtypes", "unchecked"})
public class EnumArgument implements ArgumentProvider<Enum> {

    private static Class<? extends Enum> enumClass;

    @Override
    public @Nullable Enum provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        enumClass = (Class<? extends Enum>) arg.getParameter().getType();

        Enum value = null;
        try {
            value = Enum.valueOf(enumClass, arg.getString().toUpperCase(Locale.ROOT));
        } catch (IllegalArgumentException ignored) {}

        if (value == null && !arg.getParameter().ignoreFailedArgumentParse())
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid enum value.");

        return value;
    }

    @Override
    public @NotNull List<String> suggest(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        enumClass = (Class<? extends Enum>) arg.getParameter().getType();

        String input = arg.getString().toLowerCase(Locale.ROOT);
        List<String> completions = new ArrayList<>();

        for (Enum value : enumClass.getEnumConstants()) {
            String name = value.name().toLowerCase(Locale.ROOT);
            if (name.startsWith(input)) completions.add(name);
        }

        return completions;
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class UUIDArgument implements ArgumentProvider<UUID> {
    @Override
    public @Nullable UUID provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        try {
            return UUID.fromString(arg.getString());
        } catch (Exception ex) {
            if (arg.getParameter().ignoreFailedArgumentParse()) return null;
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid UUID.");
        }
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.annotation.argument.Range;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.text.DecimalFormat;

public class DoubleArgument implements ArgumentProvider<Double> {
    private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#.#");

    @Override
    public @Nullable Double provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        double input;
        try {
            input = Double.parseDouble(arg.getString());
        } catch (NumberFormatException e) {
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid number.");
        }

        if (arg.getParameter().hasRange()) {
            Range range = arg.getParameter().getRange();

            if (!Double.isNaN(range.min()) && input < range.min())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at least " + NUMBER_FORMAT.format(range.min()) + ".");
            else if (!Double.isNaN(range.max()) && input > range.max())
                throw new BladeExitMessage("Error: The provided number '" + input + "' must be at most " + NUMBER_FORMAT.format(range.max()) + ".");
        }

        return input;
    }
}


text:
package me.vaperion.blade.argument.impl;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class BooleanArgument implements ArgumentProvider<Boolean> {
    private static final Map<String, Boolean> BOOLEAN_MAP = new HashMap<>();

    static {
        BOOLEAN_MAP.put("true", true);
        BOOLEAN_MAP.put("false", false);
        BOOLEAN_MAP.put("yes", true);
        BOOLEAN_MAP.put("no", false);
        BOOLEAN_MAP.put("1", true);
        BOOLEAN_MAP.put("0", false);
    }

    @Override
    public @Nullable Boolean provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        Boolean bool = BOOLEAN_MAP.get(arg.getString().toLowerCase(Locale.ROOT));

        if (bool == null) {
            if (arg.getParameter().ignoreFailedArgumentParse()) return null;
            throw new BladeExitMessage("Error: '" + arg.getString() + "' is not a valid logical value.");
        }

        return bool;
    }
}


text:
package me.vaperion.blade.container;

import me.vaperion.blade.Blade;
import me.vaperion.blade.command.Command;
import org.jetbrains.annotations.NotNull;

public interface ContainerCreator<T extends Container> {
    @NotNull
    T create(@NotNull Blade blade, @NotNull Command command, @NotNull String alias) throws Exception;
}


text:
package me.vaperion.blade.container;

import me.vaperion.blade.Blade;
import me.vaperion.blade.command.Command;
import org.jetbrains.annotations.NotNull;

public interface Container {
    @NotNull Blade getBlade();

    @NotNull Command getBaseCommand();
}


text:
package me.vaperion.blade.util;

import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@RequiredArgsConstructor
public abstract class PaginatedOutput<T> {

    private final int resultsPerPage;

    public abstract String formatErrorMessage(Error error, Object... args);

    public abstract String getHeader(int page, int totalPages);

    public abstract String getFooter(int page, int totalPages);

    public abstract String formatLine(T result, int index);

    public final List<String> generatePage(List<T> results, int page) {
        if (results.size() == 0) {
            return Collections.singletonList(formatErrorMessage(Error.NO_RESULTS));
        }

        int totalPages = results.size() / resultsPerPage + (results.size() % resultsPerPage == 0 ? 0 : 1);
        if (page < 1 || page > totalPages) {
            return Collections.singletonList(formatErrorMessage(Error.PAGE_OUT_OF_BOUNDS, page, totalPages));
        }

        int startIndex = (page - 1) * resultsPerPage;
        int endIndex = Math.min(startIndex + resultsPerPage, results.size());

        List<String> lines = new ArrayList<>();

        String header = getHeader(page, totalPages);
        if (header != null) lines.addAll(Arrays.asList(header.split("\n", -1)));

        for (T result : results.subList(startIndex, endIndex)) {
            String line = formatLine(result, startIndex + lines.size());
            if (line == null) continue;
            lines.addAll(Arrays.asList(line.split("\n", -1)));
        }

        String footer = getFooter(page, totalPages);
        if (footer != null) lines.addAll(Arrays.asList(footer.split("\n", -1)));

        return lines;
    }

    public enum Error {
        NO_RESULTS,
        PAGE_OUT_OF_BOUNDS
    }

}


text:
package me.vaperion.blade.util;

import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;
import java.util.function.Supplier;

public class LoadedValue<T> {

    private T value;
    @Getter private boolean loaded;

    @Nullable
    public T get() {
        if (!loaded) throw new IllegalStateException("Value is not loaded!");
        return value;
    }

    @NotNull
    public T ensureGet() {
        return Objects.requireNonNull(get());
    }

    @Nullable
    public T getOrLoad(@NotNull Supplier<T> supplier) {
        if (!loaded) set(supplier.get());
        return get();
    }

    @NotNull
    public T ensureGetOrLoad(@NotNull Supplier<T> supplier) {
        return Objects.requireNonNull(getOrLoad(supplier));
    }

    public void set(@Nullable T value) {
        this.value = value;
        loaded = true;
    }

}


text:
package me.vaperion.blade.util;

import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@UtilityClass
public class ClassUtil {

    public boolean classExists(@NotNull String className) {
        try {
            Class.forName(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    @NotNull
    public List<Class<?>> getClassesInPackage(@NotNull Class<?> clazz, @NotNull String packageName) {
        List<Class<?>> classes = new ArrayList<>();

        String packagePath = packageName.replace(".", "/");

        CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
        URL resource = codeSource.getLocation();
        String path = resource.getPath().replace("%20", " ");

        String jarPath = path.contains("!") ? path.substring(0, path.lastIndexOf("!")) : path;
        if (jarPath.startsWith("file:")) jarPath = jarPath.substring(5);

        try (JarFile jarFile = new JarFile(jarPath)) {
            Enumeration<JarEntry> entries = jarFile.entries();

            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();

                if (name.startsWith(packagePath) && name.endsWith(".class")) {
                    String className = name.substring(0, name.lastIndexOf(".")).replace("/", ".");

                    try {
                        classes.add(Class.forName(className));
                    } catch (ClassNotFoundException ignored) {}
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to get classes in package " + packageName, e);
        }

        return classes;
    }

    public Class<?> getGenericOrRawType(Parameter parameter) {
        Type type = parameter.getParameterizedType();
        if (type instanceof ParameterizedType) {
            return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
        }
        return parameter.getType();
    }

}


text:
package me.vaperion.blade.util;

import java.util.function.Function;

public final class Preconditions {

    private Preconditions() {}

    public static boolean checkNotNull(Object object, String message) {
        if (object == null) throw new NullPointerException(message);
        return true;
    }

    public static boolean checkState(boolean expression, String message) {
        if (!expression) throw new IllegalStateException(message);
        return true;
    }

    public static String checkNotEmpty(String string, String replacement) {
        if (string == null || string.isEmpty()) return replacement;
        return string;
    }

    public static <O, T> T runOrDefault(O object, T defaultValue, Function<O, T> function) {
        if (object == null) return defaultValue;
        return function.apply(object);
    }

}


text:
package me.vaperion.blade.util;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.argument.ArgumentProvider;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.util.List;

@SuppressWarnings({"rawtypes", "unchecked"})
@Getter
@RequiredArgsConstructor
public class Binding<T> {
    @NotNull
    @Contract("_, _, _ -> new")
    public static Binding<?> unsafe(@NotNull Class<?> type, @NotNull ArgumentProvider<?> provider, @NotNull List<Class<? extends Annotation>> annotations) {
        return new Binding(type, provider, annotations);
    }

    @NotNull
    @Contract("_, _ -> new")
    public static Binding<?> release(@NotNull Class<?> type, @NotNull List<Class<? extends Annotation>> annotations) {
        return new Release<>(type, null, annotations);
    }

    private final Class<T> type;
    private final ArgumentProvider<T> provider;
    private final List<Class<? extends Annotation>> annotations;

    public static final class Release<T> extends Binding<T> {
        public Release(Class<T> type, ArgumentProvider<T> provider, List<Class<? extends Annotation>> annotations) {
            super(type, provider, annotations);
        }
    }
}


text:
package me.vaperion.blade.util;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Tuple<A, B> {
    private A left;
    private B right;
}


text:
package me.vaperion.blade.util;

import me.vaperion.blade.command.Command;
import me.vaperion.blade.context.Context;
import org.jetbrains.annotations.NotNull;

import java.util.function.BiPredicate;

@FunctionalInterface
public interface PermissionPredicate extends BiPredicate<Context, Command> {
    boolean test(@NotNull Context context, @NotNull Command command);
}


text:
package me.vaperion.blade.service;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.util.PermissionPredicate;
import org.jetbrains.annotations.NotNull;

import java.util.Locale;

@RequiredArgsConstructor
public class PermissionTester {

    private final Blade blade;

    public boolean testPermission(@NotNull Context context, @NotNull Command command) {
        String permission = command.getPermission();

        if (permission == null || permission.isEmpty()) { // If the command doesn't have a permission, it's allowed
            return true;
        }

        if (permission.startsWith("@")) { // If the permission starts with @, it's a predicate
            String id = permission.substring(1);
            PermissionPredicate predicate = blade.getPermissionPredicates().get(id.toLowerCase(Locale.ROOT));

            if (predicate == null) { // If the predicate doesn't exist, it's allowed
                return true;
            }

            return predicate.test(context, command);
        }

        return context.sender().hasPermission(permission); // Let the sender implementation handle the permission check
    }

}


text:
package me.vaperion.blade.service;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.Argument.Type;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.command.Parameter;
import me.vaperion.blade.command.Parameter.FlagParameter;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import org.jetbrains.annotations.NotNull;

import java.util.*;

@RequiredArgsConstructor
public class CommandParser {

    private final Blade blade;

    @NotNull
    public List<Object> parseArguments(@NotNull Command command, @NotNull Context context, @NotNull String[] argArray) throws BladeExitMessage {
        List<String> args = new ArrayList<>(Arrays.asList(argArray));
        List<Object> result = new ArrayList<>(command.getParameters().size());

        try {
            List<String> arguments = command.isQuoted() ? combineQuotedArguments(args) : args;
            Map<Character, String> flags = parseFlags(command, arguments);

            int argIndex = 0, providerIndex = 0;
            for (Parameter parameter : command.getParameters()) {
                boolean flag = parameter instanceof FlagParameter;
                Argument bladeArgument = new Argument(parameter);

                String data;
                if (!flag) {
                    if (arguments.size() > argIndex) {
                        data = arguments.get(argIndex);
                        bladeArgument.setType(Type.PROVIDED);
                    } else if (parameter.isOptional()) {
                        data = parameter.getDefault();
                        bladeArgument.setType(Type.OPTIONAL);
                    } else throw new BladeUsageMessage();

                    if (parameter.isText())
                        data = arguments.size() > argIndex ? String.join(" ", arguments.subList(argIndex, arguments.size())) : data;
                } else data = ((FlagParameter) parameter).extractFrom(flags);
                bladeArgument.setString(data);

                try {
                    ArgumentProvider<?> provider = command.getProviders().get(providerIndex);
                    if (provider == null)
                        throw new BladeExitMessage("Could not find provider for type '" + parameter.getType().getCanonicalName() + "'.");

                    Object parsed;
                    if (bladeArgument.getType() == Type.OPTIONAL && bladeArgument.getParameter().defaultsToNull())
                        parsed = null;
                    else
                        parsed = provider.provide(context, bladeArgument);
                    result.add(parsed);

                    if (parsed == null && !parameter.defaultsToNull() && !parameter.ignoreFailedArgumentParse())
                        throw new BladeUsageMessage();

                    if (!flag) argIndex++;
                    providerIndex++;
                } catch (BladeExitMessage ex) {
                    throw ex;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw new BladeExitMessage("Failed to parse one of your arguments.");
                }
            }

            return result;
        } catch (BladeExitMessage ex) {
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new BladeExitMessage("An exception was thrown while parsing your arguments.");
        }
    }

    @NotNull
    public Map<Character, String> parseFlags(@NotNull Command command, @NotNull List<String> args) throws BladeExitMessage {
        Map<Character, String> map = new LinkedHashMap<>();
        Character pendingFlag = null;

        Iterator<String> it = args.iterator();
        while (it.hasNext()) {
            String arg = it.next();

            if (pendingFlag != null) {
                it.remove();
                map.put(pendingFlag, arg);
                pendingFlag = null;
            }

            if (arg.length() == 2 && arg.charAt(0) == '-') {
                char flag = arg.charAt(1);

                FlagParameter flagParameter = command.getFlagParameters().stream()
                      .filter(param -> param.getFlag().value() == flag)
                      .findFirst().orElse(null);
                if (flagParameter == null) continue;

                it.remove();

                if (flagParameter.isBooleanFlag())
                    map.put(flag, "true");
                else pendingFlag = flag;
            }
        }

        if (pendingFlag != null)
            throw new BladeExitMessage("The '-" + pendingFlag + "' flag requires a value.");

        return map;
    }

    @NotNull
    public static List<String> combineQuotedArguments(@NotNull List<String> args) {
        String whole = String.join(" ", args);
        List<String> arguments = new ArrayList<>(args.size());

        char boundary = '\0';
        StringBuilder building = new StringBuilder();

        for (char c : whole.toCharArray()) {
            if (c == '"' || c == '\'') {
                if (boundary == '\0') {
                    boundary = c;
                    building.setLength(0);
                    continue;
                }

                if (boundary == c) {
                    boundary = '\0';
                    arguments.add(building.toString());
                    building.setLength(0);
                    continue;
                }
            }

            if (c == ' ' && boundary == '\0') {
                if (building.length() > 0) {
                    arguments.add(building.toString());
                    building.setLength(0);
                }
                continue;
            }

            building.append(c);
        }

        if (building.length() > 0) {
            arguments.add(building.toString());
        }

        return arguments;
    }

}


text:
package me.vaperion.blade.service;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.command.Command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

@RequiredArgsConstructor
public class CommandRegistrar {

    private static final List<Command> EMPTY_COMMAND_LIST = new ArrayList<>();

    private final Blade blade;

    public void registerClass(@Nullable Object instance, @NotNull Class<?> clazz) {
        try {
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(me.vaperion.blade.annotation.command.Command.class)) continue;
                if ((instance == null) != Modifier.isStatic(method.getModifiers())) continue;

                registerMethod(instance, method);
            }
        } catch (Exception ex) {
            System.err.println("An exception was thrown while registering commands in class " + clazz.getCanonicalName() + " (instance: " + instance + ")");
            ex.printStackTrace();
        }
    }

    public void unregisterClass(@Nullable Object instance, @NotNull Class<?> clazz) {
        try {
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(me.vaperion.blade.annotation.command.Command.class)) continue;
                if ((instance == null) != Modifier.isStatic(method.getModifiers())) continue;

                unregisterMethod(instance, method);
            }
        } catch (Exception ex) {
            System.err.println("An exception was thrown while registering commands in class " + clazz.getCanonicalName() + " (instance: " + instance + ")");
            ex.printStackTrace();
        }
    }

    public void registerMethod(@Nullable Object instance, @NotNull Method method) throws Exception {
        Command cmd = new Command(blade, instance, method);
        blade.getCommands().add(cmd);

        for (String alias : cmd.getAliases()) {
            String realAlias = alias.split(" ")[0];

            blade.getAliasToCommands().computeIfAbsent(realAlias, $ -> new LinkedList<>()).add(cmd);

            if (blade.getContainers().containsKey(realAlias)) continue;
            blade.getContainers().put(realAlias, blade.getPlatform().getContainerCreator().create(blade, cmd, realAlias));
        }
    }

    public void unregisterMethod(@Nullable Object instance, @NotNull Method method) {
        me.vaperion.blade.annotation.command.Command command = method.getAnnotation(me.vaperion.blade.annotation.command.Command.class);

        String[] aliases = command.value();
        aliases = Arrays.stream(aliases).map(String::toLowerCase).toArray(String[]::new);

        Command cmd = blade.getCommands().stream().filter(c -> c.getInstance() == instance && c.getMethod() == method).findFirst().orElse(null);
        if (cmd == null) return;
        blade.getCommands().remove(cmd);

        for (String alias : aliases) {
            String realAlias = alias.split(" ")[0];

            List<Command> commandList = blade.getAliasToCommands().getOrDefault(realAlias, EMPTY_COMMAND_LIST);
            commandList.remove(cmd);
            if (commandList.isEmpty()) blade.getAliasToCommands().remove(realAlias);
        }
    }

    public void unregisterAlias(@NotNull String alias) {
        List<Runnable> calls = new ArrayList<>();

        for (Command command : blade.getCommands()) {
            String[] aliases = command.getAliases();
            if (Arrays.stream(aliases).noneMatch(a -> a.equalsIgnoreCase(alias))) continue;

            calls.add(() -> unregisterMethod(command.getInstance(), command.getMethod()));
        }

        calls.forEach(Runnable::run);
    }
}


text:
package me.vaperion.blade.service;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.annotation.argument.*;
import me.vaperion.blade.annotation.command.*;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.argument.Provider;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.util.Tuple;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
public class CommandResolver {

    private static final List<Class<? extends Annotation>> INTERNAL_ANNOTATIONS = Arrays.asList(
          Completer.class, Data.class, Flag.class, Name.class, Optional.class, Range.class, Sender.class, Text.class,

          Async.class, me.vaperion.blade.annotation.command.Command.class, Description.class, ExtraUsage.class, Hidden.class,
          ParseQuotes.class, Permission.class, Usage.class, UsageAlias.class
    );

    private final Blade blade;

    @Nullable
    public Tuple<Command, String> resolveCommand(@NotNull String[] input) {
        if (input.length == 0) return null;
        String[] commandParts = Arrays.copyOf(input, input.length);
        if (commandParts[0].contains(":")) commandParts[0] = commandParts[0].split(":", 2)[1];

        String baseCommand = commandParts[0].toLowerCase();
        List<Command> tree = blade.getAliasToCommands().getOrDefault(baseCommand, new ArrayList<>());

        do {
            String checking = String.join(" ", commandParts);

            for (Command subCommand : tree) {
                for (String commandAlias : subCommand.getAliases()) {
                    if (commandAlias.equalsIgnoreCase(checking)) return new Tuple<>(subCommand, commandAlias);
                }
            }

            commandParts = Arrays.copyOfRange(commandParts, 0, commandParts.length - 1);
        } while (commandParts.length > 0);

        return null;
    }

    @SuppressWarnings("unchecked")
    @Nullable
    public <T> ArgumentProvider<T> recursiveResolveProvider(Class<T> clazz, List<Annotation> annotations) {
        Class<?> parent = clazz;
        do {
            ArgumentProvider<T> provider = (ArgumentProvider<T>) resolveProvider(parent, annotations);
            if (provider != null) return provider;

            parent = parent.getSuperclass();
        } while (parent != Object.class && parent != null);

        return null;
    }

    @SuppressWarnings("unchecked")
    @Nullable
    private <T> ArgumentProvider<T> resolveProvider(Class<T> clazz, List<Annotation> annotations) {
        List<Class<? extends Annotation>> inputAnnotations = annotations.stream()
              .map(Annotation::annotationType)
              .map(c -> (Class<? extends Annotation>) c)
              .collect(Collectors.toList());
        inputAnnotations.removeIf(INTERNAL_ANNOTATIONS::contains);

        return blade.getProviders().stream()
              .filter(container -> container.getType() == clazz)
              .filter(container -> container.doAnnotationsMatch(inputAnnotations))
              .limit(1)
              .map(Provider::getProvider)
              .map(provider -> (ArgumentProvider<T>) provider)
              .findFirst().orElse(null);
    }

}


text:
package me.vaperion.blade.service;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.Argument.Type;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.command.Parameter;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.context.WrappedSender;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.util.Tuple;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

@RequiredArgsConstructor
public class CommandCompleter {

    private final Blade blade;

    @Nullable
    public List<String> suggest(@NotNull String commandLine, @NotNull Supplier<WrappedSender<?>> senderSupplier) {
        List<String> suggestions = new ArrayList<>();
        suggestSubCommand(suggestions, commandLine, senderSupplier);

        String[] commandParts = commandLine.split(" ");
        Tuple<Command, String> resolved = blade.getResolver().resolveCommand(commandParts);

        if (resolved == null) return suggestions.isEmpty() ? null : suggestions;
        if (resolved.getLeft().isContextBased())
            return suggestions.isEmpty() ? null : suggestions;

        Command command = resolved.getLeft();
        String foundAlias = resolved.getRight();

        List<String> argList = new ArrayList<>(Arrays.asList(commandParts));
        argList.subList(0, Math.max(1, foundAlias.split(" ").length)).clear();

        if (commandLine.endsWith(" ")) argList.add("");
        String[] actualArguments = argList.toArray(new String[0]);

        Context context = new Context(blade, senderSupplier.get(), foundAlias, actualArguments);
        if (!blade.getPermissionTester().testPermission(context, command))
            return suggestions.isEmpty() ? null : suggestions;

        suggest(suggestions, context, command, actualArguments);
        return suggestions;
    }

    public void suggest(@NotNull List<String> suggestions, @NotNull Context context,
                        @NotNull Command command, @NotNull String[] args) throws BladeExitMessage {
        if (command.isContextBased()) return;

        try {
            List<String> argumentList = new ArrayList<>(Arrays.asList(args));
            List<String> arguments = command.isQuoted() ? CommandParser.combineQuotedArguments(argumentList) : argumentList;

            Map<Character, String> flags = blade.getParser().parseFlags(command, arguments);
            for (Map.Entry<Character, String> entry : flags.entrySet()) {
                arguments.remove("-" + entry.getKey());

                boolean isFlag = command.getFlagParameters().stream().anyMatch(flag -> flag.getFlag().value() == entry.getKey());
                if (!isFlag || !"true".equals(entry.getValue())) arguments.remove(entry.getValue());
            }

            if (arguments.size() == 0) return;
            if (command.getParameterProviders().size() < arguments.size()) return;

            int index = Math.max(0, arguments.size() - 1);
            String argument = index < arguments.size() ? arguments.get(index) : "";

            Parameter parameter = index < command.getParameters().size() ? command.getParameters().get(index) : null;
            ArgumentProvider<?> parameterProvider = parameter != null && parameter.hasCustomCompleter()
                  ? parameter.getCustomCompleter() : command.getParameterProviders().get(index);

            if (parameterProvider == null) {
                throw new BladeExitMessage("Could not find provider for argument " + index + ".");
            }

            Argument bladeArgument = new Argument(parameter);
            bladeArgument.setType(index < arguments.size() ? Type.PROVIDED : Type.OPTIONAL);
            bladeArgument.setString(argument);
            if (parameter != null) bladeArgument.getData().addAll(parameter.getData());

            List<String> suggested = parameterProvider.suggest(context, bladeArgument);
            suggestions.addAll(suggested);
        } catch (BladeExitMessage ex) {
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new BladeExitMessage("An exception was thrown while parsing your arguments.");
        }
    }

    public void suggestSubCommand(@NotNull List<String> suggestions, @NotNull String commandLine, @NotNull Supplier<WrappedSender<?>> senderSupplier) throws BladeExitMessage {
        String[] commandLineParts = commandLine.split(" ");
        if (commandLineParts.length == 0) return;
        String baseCommand = commandLineParts[0];

        List<Command> commandsWithBase = blade.getAliasToCommands().get(baseCommand);
        if (commandsWithBase == null) return;

        int currentWordIndex = commandLineParts.length - 1 + (commandLine.endsWith(" ") ? 1 : 0);
        if (currentWordIndex == 0) return;

        for (Command bladeCommand : commandsWithBase) {
            if (bladeCommand.isHidden()) continue;

            Context context = new Context(blade, senderSupplier.get(), bladeCommand.getAliases()[0], new String[0]);
            if (!blade.getPermissionTester().testPermission(context, bladeCommand)) continue;

            for (String alias : bladeCommand.getAliases()) {
                if (!alias.startsWith(commandLine.toLowerCase(Locale.ROOT))) continue;

                String[] aliasWords = alias.split(" ");
                if (aliasWords.length < currentWordIndex) continue;

                String currentWord = aliasWords[currentWordIndex];
                if (currentWord.isEmpty() || suggestions.contains(currentWord)) continue;

                suggestions.add(currentWord);
            }
        }
    }

}


text:
package me.vaperion.blade.platform;

import me.vaperion.blade.Blade.Builder;
import me.vaperion.blade.container.ContainerCreator;
import org.jetbrains.annotations.NotNull;

public interface BladePlatform {
    @NotNull Object getPluginInstance();

    @NotNull ContainerCreator<?> getContainerCreator();

    void configureBlade(@NotNull Builder builder, @NotNull BladeConfiguration configuration);
}


text:
package me.vaperion.blade.platform;

import me.vaperion.blade.command.Command;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import org.jetbrains.annotations.NotNull;

import java.util.List;

@FunctionalInterface
public interface HelpGenerator {
    @NotNull
    List<String> generate(@NotNull Context context, @NotNull List<Command> commands);

    final class Default implements HelpGenerator {
        @NotNull
        @Override
        public List<String> generate(@NotNull Context context, @NotNull List<Command> commands) {
            throw new BladeExitMessage("This command failed to execute as we couldn't find its registration.");
        }
    }
}


text:
package me.vaperion.blade.platform;

import lombok.Getter;
import lombok.Setter;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.util.Preconditions;

import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

@Setter
@Getter
public final class BladeConfiguration {

    private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    private Object pluginInstance;

    private boolean overrideCommands;
    private String fallbackPrefix;
    private String defaultPermissionMessage = "You don't have permission to perform this command.";
    private long executionTimeWarningThreshold = 5L;

    private Consumer<Runnable> asyncExecutor = EXECUTOR_SERVICE::execute;

    private HelpGenerator helpGenerator;
    private TabCompleter tabCompleter;

    private Comparator<Command> helpSorter = Comparator.comparing(Command::getUsageAlias);

    public void validate() {
        Preconditions.checkNotNull(pluginInstance, "Plugin instance cannot be null.");
        Preconditions.checkNotNull(fallbackPrefix, "Fallback prefix cannot be null.");
        Preconditions.checkNotNull(helpGenerator, "Help generator cannot be null.");
        Preconditions.checkNotNull(tabCompleter, "Tab completer cannot be null.");
    }

}


text:
package me.vaperion.blade.platform;

import me.vaperion.blade.Blade;
import org.jetbrains.annotations.NotNull;

public interface TabCompleter {
    void init(@NotNull Blade blade);

    default boolean isDefault() {
        return false;
    }

    final class Default implements TabCompleter {
        @Override
        public void init(@NotNull Blade blade) {
        }

        @Override
        public boolean isDefault() {
            return true;
        }
    }
}


text:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


text:
package me.vaperion.blade.velocity;

import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.Blade.Builder.Binder;
import me.vaperion.blade.container.ContainerCreator;
import me.vaperion.blade.platform.BladeConfiguration;
import me.vaperion.blade.platform.BladePlatform;
import me.vaperion.blade.platform.TabCompleter;
import me.vaperion.blade.velocity.argument.PlayerArgument;
import me.vaperion.blade.velocity.container.VelocityContainer;
import me.vaperion.blade.velocity.platform.VelocityHelpGenerator;
import org.jetbrains.annotations.NotNull;

@RequiredArgsConstructor
public final class BladeVelocityPlatform implements BladePlatform {

    private final ProxyServer proxyServer;

    @Override
    public @NotNull Object getPluginInstance() {
        return proxyServer;
    }

    @Override
    public @NotNull ContainerCreator<?> getContainerCreator() {
        return VelocityContainer.CREATOR;
    }

    @Override
    public void configureBlade(Blade.@NotNull Builder builder, @NotNull BladeConfiguration configuration) {
        configuration.setPluginInstance(proxyServer);
        configuration.setFallbackPrefix("velocity");
        configuration.setHelpGenerator(new VelocityHelpGenerator());
        configuration.setTabCompleter(new TabCompleter.Default());

        Binder binder = new Binder(builder, true);
        binder.bind(Player.class, new PlayerArgument());
    }
}


text:
package me.vaperion.blade.velocity.context;

import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.context.WrappedSender;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@RequiredArgsConstructor
public class VelocitySender implements WrappedSender<CommandSource> {
    private final CommandSource commandSource;

    @Override
    public @NotNull CommandSource getSender() {
        return commandSource;
    }

    @Override
    public @NotNull String getName() {
        if (commandSource instanceof Player)
            return ((Player) commandSource).getUsername();
        else if (commandSource instanceof ConsoleCommandSource)
            return "Console";
        return "<unknown>";
    }

    @Override
    public boolean hasPermission(@NotNull String permissionNode) {
        if ("console".equals(permissionNode))
            return commandSource instanceof ConsoleCommandSource;
        return commandSource.hasPermission(permissionNode);
    }

    @Override
    public void sendMessage(@NotNull String message) {
        commandSource.sendMessage(Component.text(message));
    }

    @Override
    public void sendMessage(@NotNull String... messages) {
        for (String message : messages)
            sendMessage(message);
    }

    @SuppressWarnings("unchecked")
    @Nullable
    @Override
    public <T> T parseAs(@NotNull Class<T> clazz) {
        if (clazz.equals(Player.class) && commandSource instanceof Player)
            return (T) commandSource;
        else if (clazz.equals(ConsoleCommandSource.class) && commandSource instanceof ConsoleCommandSource)
            return (T) commandSource;
        else if (clazz.equals(CommandSource.class))
            return (T) commandSource;

        return null;
    }
}


text:
package me.vaperion.blade.velocity.command;

import com.velocitypowered.api.command.CommandSource;
import me.vaperion.blade.annotation.argument.Flag;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.command.Parameter.CommandParameter;
import me.vaperion.blade.command.Parameter.FlagParameter;
import me.vaperion.blade.command.UsageMessage;
import me.vaperion.blade.context.Context;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;

public final class VelocityUsageMessage implements UsageMessage {

    private final Component component;
    private String toString;

    public VelocityUsageMessage(Command command) {
        this(command, true);
    }

    public VelocityUsageMessage(Command command, boolean addPrefix) {
        Component component = Component.text((addPrefix ? "Usage: " : "") + "/").color(NamedTextColor.RED)
              .hoverEvent(HoverEvent.showText(
                    Component.text(command.getDescription()).color(NamedTextColor.GRAY))
              ).append(
                    Component.text(command.getUsageAlias())
              );

        if (!command.getCustomUsage().isEmpty()) {
            this.component = component.append(
                  Component.text(command.getCustomUsage())
            );
            return;
        }

        // Add flag parameters
        boolean first = true;
        for (FlagParameter flagParameter : command.getFlagParameters()) {
            Flag flag = flagParameter.getFlag();

            if (first) {
                component = component.append(
                      Component.text(" (").color(NamedTextColor.RED)
                );
                first = false;
            } else {
                component = component.append(
                      Component.text(" | ").color(NamedTextColor.RED)
                ).hoverEvent(HoverEvent.showText(
                      Component.text(command.getDescription()).color(NamedTextColor.GRAY))
                );
            }

            component = component.append(
                        Component.text("-" + flag.value() + (flagParameter.isBooleanFlag() ? "" : " <" + flagParameter.getName() + ">"))
                  ).color(NamedTextColor.AQUA)
                  .hoverEvent(HoverEvent.showText(
                        Component.text(flag.description()).color(NamedTextColor.GRAY))
                  );
        }
        if (!first) component = component.append(
              Component.text(")").color(NamedTextColor.RED)
        ).hoverEvent(HoverEvent.showText(
              Component.text(command.getDescription()).color(NamedTextColor.GRAY))
        );

        // Add real parameters
        for (CommandParameter commandParameter : command.getCommandParameters()) {
            component = component.append(
                  Component.text(" ")
            );

            component = component.append(
                  Component.text(commandParameter.isOptional() ? "(" : "<")
            );
            component = component.append(
                  Component.text(commandParameter.getName())
            );
            if (commandParameter.isText()) component = component.append(
                  Component.text("...")
            );
            component = component.append(
                  Component.text(commandParameter.isOptional() ? ")" : ">")
            );
        }

        // Add extra usage
        if (!command.getExtraUsageData().isEmpty()) {
            component = component.append(
                  Component.text(" " + command.getExtraUsageData().trim())
            );
        }

        this.component = component;
    }

    @Override
    public void sendTo(@NotNull Context context) {
        ((CommandSource) context.sender().getSender()).sendMessage(this.component);
    }

    @NotNull
    @Override
    public String toString() {
        if (toString == null) toString = LegacyComponentSerializer.legacyAmpersand().serialize(component);
        return toString;
    }
}


text:
package me.vaperion.blade.velocity.argument;

import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.Argument.Type;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

public class PlayerArgument implements ArgumentProvider<Player> {

    public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}");

    @Override
    public @Nullable Player provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        ProxyServer proxyServer = (ProxyServer) ctx.blade().getPlatform().getPluginInstance();

        Player player = ctx.sender().parseAs(Player.class);

        if (arg.getType() == Type.OPTIONAL && "self".equals(arg.getString())) {
            if (player != null) return player;
            else
                throw new BladeUsageMessage(); // show usage to console if we have 'self' as a default value (only works on players)
        }

        Player onlinePlayer = getPlayer(proxyServer, arg.getString());
        if (onlinePlayer == null && !arg.getParameter().ignoreFailedArgumentParse())
            throw new BladeExitMessage("Error: No online player with name or UUID '" + arg.getString() + "' found.");

        return onlinePlayer;
    }

    @Override
    public @NotNull
    List<String> suggest(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        ProxyServer proxyServer = (ProxyServer) ctx.blade().getPlatform().getPluginInstance();

        List<String> completions = new ArrayList<>();
        String input = arg.getString();

        for (Player player : proxyServer.getAllPlayers()) {
            if (player.getUsername().toLowerCase().startsWith(input.toLowerCase()))
                completions.add(player.getUsername());
        }

        return completions;
    }

    private boolean isUUID(@NotNull String input) {
        return UUID_PATTERN.matcher(input).matches();
    }

    @Nullable
    private Player getPlayer(@NotNull ProxyServer proxyServer, @NotNull String input) {
        if (isUUID(input)) return proxyServer.getPlayer(UUID.fromString(input)).orElse(null);
        return proxyServer.getPlayer(input).orElse(null);
    }

}


text:
package me.vaperion.blade.velocity.container;

import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import me.vaperion.blade.Blade;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.container.Container;
import me.vaperion.blade.container.ContainerCreator;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import me.vaperion.blade.util.Tuple;
import me.vaperion.blade.velocity.command.VelocityUsageMessage;
import me.vaperion.blade.velocity.context.VelocitySender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

@Getter
public class VelocityContainer implements RawCommand, Container {

    public static final ContainerCreator<VelocityContainer> CREATOR = VelocityContainer::new;
    private final Blade blade;
    private final Command baseCommand;

    private VelocityContainer(@NotNull Blade blade, @NotNull Command command, @NotNull String alias) throws Exception {
        this.blade = blade;
        this.baseCommand = command;

        ProxyServer proxyServer = (ProxyServer) blade.getPlatform().getPluginInstance();
        CommandManager commandManager = proxyServer.getCommandManager();

        CommandMeta meta = commandManager.metaBuilder(alias)
              .aliases(command.getBaseCommands())
              .build();
        commandManager.register(meta, this);
    }

    @Nullable
    private Tuple<Command, String> resolveCommand(@NotNull String[] arguments) throws BladeExitMessage {
        return blade.getResolver().resolveCommand(arguments);
    }

    @NotNull
    private String getSenderType(@NotNull Class<?> clazz) {
        switch (clazz.getSimpleName()) {
            case "Player":
                return "players";

            case "ConsoleCommandSource":
                return "the console";

            default:
                return "everyone";
        }
    }

    private void sendUsageMessage(@NotNull Context context, @Nullable Command command) {
        if (command == null) return;
        command.getUsageMessage().ensureGetOrLoad(() -> new VelocityUsageMessage(command)).sendTo(context);
    }

    private boolean hasPermission(@NotNull CommandSource sender, String[] args) throws BladeExitMessage {
        Tuple<Command, String> command = resolveCommand(joinAliasToArgs(this.baseCommand.getAliases()[0], args));
        Context context = new Context(blade, new VelocitySender(sender), command == null ? "" : command.getRight(), args);
        return checkPermission(context, command == null ? null : command.getLeft()).getLeft();
    }

    private Tuple<Boolean, String> checkPermission(@NotNull Context context, @Nullable Command command) throws BladeExitMessage {
        if (command == null)
            return new Tuple<>(false, "This command failed to execute as we couldn't find its registration.");

        return new Tuple<>(
              blade.getPermissionTester().testPermission(context, command),
              command.isHidden() ? "" : command.getPermissionMessage());
    }

    private String[] joinAliasToArgs(String alias, String[] args) {
        String[] aliasParts = alias.split(" ");
        String[] argsWithAlias = new String[args.length + aliasParts.length];
        System.arraycopy(aliasParts, 0, argsWithAlias, 0, aliasParts.length);
        System.arraycopy(args, 0, argsWithAlias, aliasParts.length, args.length);
        return argsWithAlias;
    }

    @Override
    public boolean hasPermission(Invocation invocation) {
        return hasPermission(invocation.source(), new String[0]);
    }

    @Override
    public void execute(Invocation invocation) {
        CommandSource sender = invocation.source();
        String[] args = invocation.arguments().split(" ");
        String alias = invocation.alias();

        Command command = null;
        String resolvedAlias;

        String[] joined = joinAliasToArgs(alias, args);
        Context context = new Context(blade, new VelocitySender(sender), alias, args);

        try {
            Tuple<Command, String> resolved = resolveCommand(joined);
            if (resolved == null) {
                List<Command> availableCommands = blade.getCommands()
                      .stream().filter(c -> Arrays.stream(c.getAliases()).anyMatch(a -> a.toLowerCase().startsWith(alias.toLowerCase(Locale.ROOT) + " ") || a.equalsIgnoreCase(alias)))
                      .collect(Collectors.toList());

                for (String line : blade.getConfiguration().getHelpGenerator().generate(context, availableCommands)) {
                    sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(line));
                }

                return;
            }

            Tuple<Boolean, String> permissionResult = checkPermission(context, resolved.getLeft());
            if (!permissionResult.getLeft()) throw new BladeExitMessage(permissionResult.getRight());

            command = resolved.getLeft();
            resolvedAlias = resolved.getRight();
            int offset = Math.min(args.length, resolvedAlias.split(" ").length - 1);

            if (command.isHasSenderParameter() && !command.getSenderType().isInstance(sender))
                throw new BladeExitMessage("This command can only be executed by " + getSenderType(command.getSenderType()) + ".");

            final Command finalCommand = command;
            final String finalResolvedAlias = resolvedAlias;

            if (finalCommand.getMethod() == null) {
                throw new BladeExitMessage("The command " + finalResolvedAlias + " is a root command and cannot be executed.");
            }

            Runnable runnable = () -> {
                try {
                    List<Object> parsed;
                    if (finalCommand.isContextBased()) {
                        parsed = Collections.singletonList(context);
                    } else {
                        parsed = blade.getParser().parseArguments(finalCommand, context, Arrays.copyOfRange(args, offset, args.length));
                        if (finalCommand.isHasSenderParameter()) parsed.add(0, sender);
                    }

                    finalCommand.getMethod().invoke(finalCommand.getInstance(), parsed.toArray(new Object[0]));
                } catch (BladeUsageMessage ex) {
                    sendUsageMessage(context, finalCommand);
                } catch (BladeExitMessage ex) {
                    sender.sendMessage(Component.text(ex.getMessage()).color(NamedTextColor.RED));
                } catch (InvocationTargetException ex) {
                    if (ex.getTargetException() != null) {
                        if (ex.getTargetException() instanceof BladeUsageMessage) {
                            sendUsageMessage(context, finalCommand);
                            return;
                        } else if (ex.getTargetException() instanceof BladeExitMessage) {
                            sender.sendMessage(Component.text(ex.getTargetException().getMessage()).color(NamedTextColor.RED));
                            return;
                        }
                    }

                    ex.printStackTrace();
                    sender.sendMessage(Component.text("An exception was thrown while executing this command.").color(NamedTextColor.RED));
                } catch (Throwable t) {
                    t.printStackTrace();
                    sender.sendMessage(Component.text("An exception was thrown while executing this command.").color(NamedTextColor.RED));
                }
            };

            if (command.isAsync()) {
                blade.getConfiguration().getAsyncExecutor().accept(runnable);
            } else {
                long time = System.nanoTime();
                runnable.run();
                long elapsed = (System.nanoTime() - time) / 1000000;

                if (elapsed >= blade.getConfiguration().getExecutionTimeWarningThreshold()) {
                    System.out.printf(
                          "[Blade] Command '%s' (%s#%s) took %d milliseconds to execute!%n",
                          finalResolvedAlias,
                          finalCommand.getMethod().getDeclaringClass().getName(),
                          finalCommand.getMethod().getName(),
                          elapsed
                    );
                }
            }
        } catch (BladeUsageMessage ex) {
            sendUsageMessage(context, command);
        } catch (BladeExitMessage ex) {
            sender.sendMessage(Component.text(ex.getMessage()).color(NamedTextColor.RED));
        } catch (Throwable t) {
            t.printStackTrace();
            sender.sendMessage(Component.text("An exception was thrown while executing this command.").color(NamedTextColor.RED));
        }
    }

    @Override
    public List<String> suggest(Invocation invocation) {
        CommandSource sender = invocation.source();
        String[] args = invocation.arguments().split(" ");
        String alias = invocation.alias();

        if (!blade.getConfiguration().getTabCompleter().isDefault()) return Collections.emptyList();
        if (!hasPermission(sender, args)) return Collections.emptyList();

        try {
            Tuple<Command, String> resolved = resolveCommand(joinAliasToArgs(alias, args));
            if (resolved == null) {
                // maybe suggest subcommands?
                return Collections.emptyList();
            }

            Command command = resolved.getLeft();
            String foundAlias = resolved.getRight();

            List<String> argList = new ArrayList<>(Arrays.asList(args));
            if (foundAlias.split(" ").length > 1) argList.subList(0, foundAlias.split(" ").length - 1).clear();

            if (argList.isEmpty()) argList.add("");
            String[] actualArguments = argList.toArray(new String[0]);

            Context context = new Context(blade, new VelocitySender(sender), foundAlias, actualArguments);

            List<String> suggestions = new ArrayList<>();
            blade.getCompleter().suggest(suggestions, context, command, actualArguments);
            return suggestions;
        } catch (BladeExitMessage ex) {
            sender.sendMessage(Component.text(ex.getMessage()).color(NamedTextColor.RED));
        } catch (Exception ex) {
            ex.printStackTrace();
            sender.sendMessage(Component.text("An exception was thrown while completing this command.").color(NamedTextColor.RED));
        }

        return Collections.emptyList();
    }
}


text:
package me.vaperion.blade.velocity.platform;

import me.vaperion.blade.command.Command;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.platform.HelpGenerator;
import me.vaperion.blade.util.PaginatedOutput;
import me.vaperion.blade.velocity.command.VelocityUsageMessage;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class VelocityHelpGenerator implements HelpGenerator {

    private static final Pattern COLOR_PATTERN = Pattern.compile("&([0-9a-fk-or])");

    @Override
    public @NotNull List<String> generate(@NotNull Context context, @NotNull List<Command> commands) {
        commands = commands.stream()
              .distinct()
              .filter(c -> !c.isHidden())
              .sorted(context.blade().getConfiguration().getHelpSorter())
              .collect(Collectors.toList());

        int originalCount = commands.size();
        commands = commands.stream()
              .filter(c -> context.blade().getPermissionTester().testPermission(context, c))
              .collect(Collectors.toList());

        if (originalCount != 0 && commands.isEmpty()) {
            return Collections.singletonList("&c" + context.blade().getConfiguration().getDefaultPermissionMessage());
        }

        return new PaginatedOutput<Command>(10) {
            @Override
            public String formatErrorMessage(Error error, Object... args) {
                switch (error) {
                    case NO_RESULTS:
                        return "&cNo results found.";
                    case PAGE_OUT_OF_BOUNDS:
                        return String.format("&cPage %d does not exist, valid range is 1 to %d.", args);
                }
                return null;
            }

            @Override
            public String getHeader(int page, int totalPages) {
                return "&b==== &eHelp for /" + context.alias() + "&b ====";
            }

            @Override
            public String getFooter(int page, int totalPages) {
                return "&b==== &ePage " + page + "/" + totalPages + "&b ====";
            }

            @Override
            public String formatLine(Command result, int index) {
                String help = stripColor(result.getUsageMessage().ensureGetOrLoad(() -> new VelocityUsageMessage(result, false)).toString());
                return "&b - &e" + help + (result.getDescription().isEmpty() ? "" : (" - &7" + result.getDescription()));
            }
        }.generatePage(commands, parsePage(context.argument(0)));
    }

    private String stripColor(String string) {
        return COLOR_PATTERN.matcher(string).replaceAll("");
    }

    private int parsePage(String argument) {
        if (argument == null) return 1;
        try {
            return Integer.parseInt(argument);
        } catch (NumberFormatException e) {
            return 1;
        }
    }
}


text:
module github.com/lu4p/binclude

go 1.15

require github.com/rogpeppe/go-internal v1.7.0

repo_language: Go

mime_type: text/x-c


text:
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.7.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=


text:
#!/usr/bin/env bash

set -e
echo "" > coverage.txt

for d in $(go list ./... | grep -v vendor); do
    go test -race -coverprofile=profile.out -covermode=atomic $d
    if [ -f profile.out ]; then
        cat profile.out >> coverage.txt
        rm profile.out
    fi
done


text:
package binclude

import (
	"bytes"
	"compress/gzip"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"
)

// Debug if set to true files are read via os.Open() and the bincluded files are
// ignored, use when developing.
var Debug = false

// Include this file/ directory (including subdirectories) relative to the package path (noop)
// The path is walked via filepath.Walk and all files found are included
// This function returns the name to make it usable in global variable definitions.
func Include(name string) string { return name }

// IncludeGlob include all files matching the given pattern
// same syntax as filepath.Glob
// This function returns an empty string to make it usable in global variable definitions.
func IncludeGlob(pattern string) string { return "" }

// IncludeFromFile like include but reads paths from a textfile.
// Paths are separated by a newline (noop)
func IncludeFromFile(name string) {}

// FileSystem implements access to a collection of named files.
type FileSystem struct {
	Files
	sync.RWMutex
}

// check that the http.FileSystem interface is implemented
var _ http.FileSystem = new(FileSystem)

// Files a map from the filepath to the files
type Files map[string]*File

// Open returns a File using the File interface
func (fs *FileSystem) Open(name string) (http.File, error) {
	if Debug {
		name = filepath.FromSlash(name)

		return os.Open(name)
	}

	name = strings.TrimPrefix(name, "./")
	if f, ok := fs.Files[name]; ok {
		f.reader = bytes.NewReader(f.Content)
		f.path = name
		f.fs = fs
		return f, nil
	}

	return nil, &os.PathError{"open", name, errors.New("File does not exist in binclude map")}
}

// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
func (fs *FileSystem) Stat(name string) (os.FileInfo, error) {
	f, err := fs.Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return f.Stat()
}

// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
func (fs *FileSystem) ReadFile(filename string) ([]byte, error) {
	f, err := fs.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return ioutil.ReadAll(f)
}

// ReadDir reads the directory named by dirname and returns
// a list of directory entries sorted by filename.
func (fs *FileSystem) ReadDir(dirname string) ([]os.FileInfo, error) {
	f, err := fs.Open(dirname)
	if err != nil {
		return nil, err
	}
	list, _ := f.Readdir(-1)
	f.Close()
	sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
	return list, nil
}

// CopyFile copies a specific file from a binclude FileSystem to the hosts FileSystem.
// Permissions are copied from the included file.
func (fs *FileSystem) CopyFile(bincludePath, hostPath string) error {
	src, err := fs.Open(bincludePath)
	if err != nil {
		return err
	}
	defer src.Close()

	info, _ := src.Stat()

	dst, err := os.OpenFile(hostPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode().Perm())
	if err != nil {
		return err
	}
	defer dst.Close()

	_, err = io.Copy(dst, src)
	if err != nil {
		return err
	}

	_, err = os.Stat(hostPath)
	return err
}

// Compression the compression algorithm to use
type Compression int

const (
	// None dont compress
	None Compression = iota
	// Gzip use gzip compression
	Gzip
)

// Decompress turns a FileSystem with compressed files into a filesystem without compressed files
func (fs *FileSystem) Decompress() (err error) {
	for path, file := range fs.Files {
		if file.Compression == None {
			continue
		}

		f, _ := fs.Open(path) // open cannot error when using a path we got from the fs
		defer f.Close()

		var compReader io.Reader
		if file.Compression == Gzip {
			compReader, err = gzip.NewReader(f)
			if err != nil {
				return fmt.Errorf("gzip err: %v", err)
			}
		}

		content, err := ioutil.ReadAll(compReader)
		if err != nil {
			return fmt.Errorf("reader err: %v", err)
		}
		f.Close()

		fs.Files[path].Content = content
	}

	return nil

}

// Compress turns a FileSystem without compressed files into a filesystem with compressed files
func (fs *FileSystem) Compress(algo Compression) error {
	if algo == None {
		return nil
	}
	for _, file := range fs.Files {
		if file.Mode.IsDir() || !shouldCompress(file.Filename) {
			continue
		}
		var b bytes.Buffer

		var writer io.WriteCloser
		if algo == Gzip {
			writer = gzip.NewWriter(&b)
		}

		_, err := writer.Write(file.Content)
		writer.Close()
		if err != nil {
			return err
		}

		file.Compression = algo
		file.Content = b.Bytes()
	}

	return nil
}

// compressExcl exclude certain files from compression which don't compress well
// inspired by https://github.com/gin-contrib/gzip/blob/master/options.go
var compressExcl = []string{".jpg", ".jpeg", ".gz", ".png", ".gif", ".zip"}

// shouldCompress says whether a file should be compressed based on its mimetype
func shouldCompress(name string) bool {
	for _, excl := range compressExcl {
		if strings.HasSuffix(name, excl) {
			return false
		}
	}
	return true
}

// File implements the http.File interface
type File struct {
	Filename string
	Mode     os.FileMode
	ModTime  time.Time
	Content  []byte
	Compression
	reader io.ReadSeeker
	path   string
	fs     *FileSystem
}

// check that the http.File interface is implemented
var _ http.File = new(File)

// Read implements the io.Reader interface.
func (f *File) Read(p []byte) (n int, err error) {
	return f.reader.Read(p)
}

// Name returns the name of the file as presented to Open.
func (f *File) Name() string {
	return f.path
}

// Close closes the File, rendering it unusable for I/O.
func (f *File) Close() error {
	f.reader = nil
	return nil
}

// Size returns the original length of the underlying byte slice.
// Size is the number of bytes available for reading via ReadAt.
// The returned value is always the same and is not affected by calls
// to any other method.
func (f *File) Size() int64 {
	return int64(len(f.Content))
}

// Readdir reads the contents of the directory associated with file and
// returns a slice of up to n FileInfo values, as would be returned
// by Lstat, in directory order. Subsequent calls on the same file will yield
// further FileInfos.
func (f *File) Readdir(count int) (infos []os.FileInfo, err error) {
	fileDir := f.Name()
	if !f.Mode.IsDir() {
		fileDir = filepath.Dir(f.path)
	}

	for path, file := range f.fs.Files {
		if filepath.Dir(path) != fileDir {
			continue
		}

		info, _ := file.Stat()

		infos = append(infos, info)
	}

	return infos, nil
}

// Stat returns the FileInfo structure describing file.
// Error is always nil
func (f *File) Stat() (os.FileInfo, error) {
	return &FileInfo{
		name:    f.Filename,
		mode:    f.Mode,
		size:    f.Size(),
		modtime: f.ModTime,
	}, nil
}

// Seek implements the io.Seeker interface.
func (f *File) Seek(offset int64, whence int) (int64, error) {
	return f.reader.Seek(offset, whence)
}

// FileInfo implements the os.FileInfo interface.
type FileInfo struct {
	name    string
	mode    os.FileMode
	modtime time.Time
	size    int64
}

// check that the os.FileInfo interface is implemented
var _ os.FileInfo = new(FileInfo)

// Name returns the base name of the file
func (info *FileInfo) Name() string {
	return info.name
}

// Size returns the length in bytes
func (info *FileInfo) Size() int64 {
	return info.size
}

// Mode returns the file mode bits
func (info *FileInfo) Mode() os.FileMode {
	return info.mode
}

// ModTime returns the modification time (returns current time)
func (info *FileInfo) ModTime() time.Time {
	return info.modtime
}

// IsDir abbreviation for Mode().IsDir()
func (info *FileInfo) IsDir() bool {
	return info.Mode().IsDir()
}

// Sys underlying data source (returns nil)
func (info *FileInfo) Sys() interface{} {
	return nil
}


text:
package binclude_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"

	"github.com/lu4p/binclude"
	"github.com/lu4p/binclude/example"
)

var BinFS = example.BinFS

func ExampleFileSystem_Open() {
	binclude.Include("./assets")
	f, _ := BinFS.Open("./assets/asset1.txt")
	data, _ := ioutil.ReadAll(f)
	fmt.Println(string(data))
	// Output: asset1
}

func ExampleFileSystem_ReadFile() {
	binclude.Include("file.txt")
	data, _ := BinFS.ReadFile("file.txt")
	fmt.Println(string(data))
	// Output: file.txt
}

func ExampleFileSystem_ReadDir() {
	binclude.Include("./assets")
	infos, _ := BinFS.ReadDir("./assets")
	for _, info := range infos {
		fmt.Println(info.Name())
	}
	// Output: asset1.txt
	// asset2.txt
	// logo_nocompress.png
	// subdir
}

func ExampleFileSystem_CopyFile() {
	BinFS.CopyFile("./assets/asset1.txt", "asset1.txt")

	c, _ := ioutil.ReadFile("asset1.txt")

	fmt.Println(string(c))

	// Output: asset1
}

func ExampleIncludeGlob() {
	binclude.IncludeGlob("./assets/*.txt")
	f, _ := BinFS.Open("./assets/asset1.txt")
	data, _ := ioutil.ReadAll(f)
	fmt.Println(string(data))
	// Output: asset1
}

func TestCopyFile(t *testing.T) {
	err := BinFS.CopyFile("./assets/asset1.txt", "asset1.txt")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("asset1.txt")

	c, err := ioutil.ReadFile("asset1.txt")
	if err != nil {
		t.Fatal(err)
	}

	if string(c) != "asset1" {
		t.Fatal("content doesn't match", c)
	}

	err = BinFS.CopyFile("nonexistent.txt", "nonexistent.txt")
	if err == nil {
		t.Fatal("can copy nonexistent file")
	}
}

func TestCompression(t *testing.T) {
	testPath := "assets/asset1.txt"
	startContent, err := BinFS.ReadFile(testPath)
	if err != nil {
		t.Fatal(err)
	}

	err = BinFS.Compress(binclude.None)
	if err != nil {
		t.Fatal(err)
	}

	nocompressContent, err := BinFS.ReadFile(testPath)
	if err != nil {
		t.Fatal(err)
	}

	if string(startContent) != string(nocompressContent) {
		t.Fatal("Compression with binclude.None should be a noop")
	}

	err = BinFS.Compress(binclude.Gzip)
	if err != nil {
		t.Fatal(err)
	}

	if BinFS.Files["assets/logo_nocompress.png"].Compression != binclude.None {
		t.Fatal("Unexpected compressed png")
	}

	gzipContent, err := BinFS.ReadFile(testPath)
	if err != nil {
		t.Fatal(err)
	}

	if string(startContent) == string(gzipContent) {
		t.Fatal("Gzip didn't compress")
	}

	err = BinFS.Decompress()
	if err != nil {
		t.Fatal(err)
	}

	decGzipContent, err := BinFS.ReadFile(testPath)
	if err != nil {
		t.Fatal(err)
	}

	if string(startContent) != string(decGzipContent) {
		t.Fatal("File differs after compression and decompression.")
	}

}

func TestReadFile(t *testing.T) {
	_, err := BinFS.ReadFile("nonexistent.txt")
	if err == nil {
		t.Fatal("shouldn't be able to read nonexistent file")
	}
}

func TestOpen(t *testing.T) {
	f, err := BinFS.Open("file.txt")
	if err != nil {
		t.Fatal(err)
	}

	data, _ := ioutil.ReadAll(f)
	if string(data) != "file.txt" {
		t.Fatal("content does not match")
	}

	f.Close()

	f, err = BinFS.Open("nonexistent.txt")
	if err == nil {
		f.Close()
		t.Fatal("nonexistent file can be opened")
	}

	binclude.Debug = true
	f, err = BinFS.Open("go.mod")
	if err != nil {
		t.Fatal("cannot use os filesystem")
	}
	f.Close()
	binclude.Debug = false

}

func TestReadDir(t *testing.T) {
	_, err := BinFS.ReadDir("./assets/asset1.txt")
	if err != nil {
		t.Fatal("cannot read directory of file")
	}

	_, err = BinFS.ReadDir("./nonexistent")
	if err == nil {
		t.Fatal("shouldn't be able to read nonexistent dir")
	}
}

func TestStat(t *testing.T) {
	_, err := BinFS.Stat("./assets/asset1.txt")
	if err != nil {
		t.Fatal("cannot stat file")
	}

	_, err = BinFS.Stat("./nonexistent")
	if err == nil {
		t.Fatal("shouldn't be able to stat nonexistent dir")
	}
}

func TestFileInfo(t *testing.T) {
	file := BinFS.Files["assets/asset1.txt"]
	info, err := BinFS.Stat("./assets/asset1.txt")
	if err != nil {
		t.Fatal("cannot stat file")
	}

	if info.Name() != file.Filename {
		t.Fatal("Name does not match")
	}

	if int(info.Size()) != len(file.Content) {
		t.Fatal("Size does not match")
	}

	if info.Mode() != file.Mode {
		t.Fatal("Mode does not match")
	}

	if info.IsDir() != file.Mode.IsDir() {
		t.Fatal("IsDir does not match")
	}

	if info.ModTime() != file.ModTime {
		t.Fatal("ModTime does not match")
	}

	if info.Sys() != nil {
		t.Fatal("Sys return should be nil")
	}
}