All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.githubapp.command.airline.runtime.AbstractCommandDispatcher Maven / Gradle / Ivy

There is a newer version: 2.8.2
Show newest version
package io.quarkiverse.githubapp.command.airline.runtime;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.jboss.logging.Logger;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GHEventPayload.IssueComment;
import org.kohsuke.github.GHReaction;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.ReactionContent;

import com.github.rvesse.airline.Cli;
import com.github.rvesse.airline.annotations.AirlineModule;
import com.github.rvesse.airline.builder.ParserBuilder;
import com.github.rvesse.airline.help.Help;
import com.github.rvesse.airline.model.MetadataLoader;
import com.github.rvesse.airline.parser.ParseResult;
import com.github.rvesse.airline.parser.errors.ParseException;
import com.github.rvesse.airline.parser.errors.handlers.CollectAll;

import io.quarkiverse.githubapp.command.airline.AirlineInject;
import io.quarkiverse.githubapp.command.airline.runtime.util.Commandline;
import io.quarkiverse.githubapp.command.airline.runtime.util.Reactions;

public abstract class AbstractCommandDispatcher {

    private static final Logger LOGGER = Logger.getLogger(AbstractCommandDispatcher.class);

    private final Cli cli;
    private final CliConfig cliConfig;
    private final Map commandConfigs;
    private final Map commandPermissionConfigs;
    private final Map commandTeamConfigs;

    protected AbstractCommandDispatcher(Class cliClass, CliConfig cliConfig) {
        ParserBuilder parserBuilder = new ParserBuilder();
        parserBuilder.withCommandFactory(new ArcCommandFactory<>());
        parserBuilder.withErrorHandler(new CollectAll());
        parserBuilder.withCompositionAnnotations(AirlineModule.class.getName(), AirlineInject.class.getName());

        this.cli = new Cli<>(MetadataLoader.loadGlobal(cliClass, parserBuilder.build()));
        this.cliConfig = cliConfig;
        this.commandConfigs = getCommandConfigs();
        this.commandPermissionConfigs = getCommandPermissionConfigs();
        this.commandTeamConfigs = getCommandTeamConfigs();
    }

    protected abstract Map getCommandConfigs();

    protected abstract Map getCommandPermissionConfigs();

    protected abstract Map getCommandTeamConfigs();

    protected Optional> getCommand(GHEventPayload.IssueComment issueCommentPayload) {
        String body = issueCommentPayload.getComment().getBody();

        if (body == null || body.isBlank()) {
            return Optional.empty();
        }

        Optional firstLineOptional = body.trim().lines().findFirst();
        if (firstLineOptional.isEmpty() || firstLineOptional.get().isBlank()) {
            return Optional.empty();
        }

        String firstLine = firstLineOptional.get().trim();
        String cliName = firstLine.split(" ", 2)[0];

        if (!matches(cliName)) {
            return Optional.empty();
        }

        List commandLine;
        try {
            commandLine = Commandline.translateCommandline(firstLine);
            commandLine.remove(0);
        } catch (IllegalArgumentException e) {
            handleParseError(issueCommentPayload, firstLine, null, e.getMessage());

            if (cliConfig.getDefaultCommandConfig().getReactionStrategy().reactionOnError()) {
                Reactions.createReaction(issueCommentPayload, ReactionContent.CONFUSED);
            }

            return Optional.empty();
        }

        ParseResult parseResult = cli.parseWithResult(commandLine);

        if (parseResult.wasSuccessful()) {
            String commandClassName = parseResult.getState().getCommand().getType().getName();

            CommandConfig commandConfig = commandConfigs.getOrDefault(commandClassName,
                    cliConfig.getDefaultCommandConfig());
            if (!commandConfig.getScope().isInScope(issueCommentPayload.getIssue().isPullRequest())) {
                return Optional.empty();
            }

            CommandPermissionConfig commandPermissionConfig = commandPermissionConfigs.getOrDefault(commandClassName,
                    cliConfig.getDefaultCommandPermissionConfig());
            CommandTeamConfig commandTeamConfig = commandTeamConfigs.getOrDefault(commandClassName,
                    cliConfig.getDefaultCommandTeamConfig());

            if (!hasPermission(commandPermissionConfig, commandTeamConfig, issueCommentPayload.getRepository(),
                    issueCommentPayload.getSender())) {
                if (commandConfig.getReactionStrategy().reactionOnError()) {
                    Reactions.createReaction(issueCommentPayload, ReactionContent.MINUS_ONE);
                }
                return Optional.empty();
            }

            GHReaction ackReaction;
            if (commandConfig.getReactionStrategy().reactionOnProgress()) {
                ackReaction = Reactions.createReaction(issueCommentPayload, ReactionContent.ROCKET);
            } else {
                ackReaction = null;
            }

            return Optional.of(new CommandExecutionContext<>(firstLine, parseResult.getCommand(), commandConfig, ackReaction));
        }

        CommandConfig bestCommandConfig = getBestCommandConfigInErrorState(parseResult);
        if (bestCommandConfig.getReactionStrategy().reactionOnError()) {
            Reactions.createReaction(issueCommentPayload, ReactionContent.CONFUSED);
        }

        handleParseError(issueCommentPayload, firstLine, parseResult, null);

        return Optional.empty();
    }

    private boolean matches(String cli) {
        for (String alias : cliConfig.getAliases()) {
            if (alias.equals(cli)) {
                return true;
            }
        }

        return false;
    }

    private boolean hasPermission(CommandPermissionConfig commandPermissionConfig,
            CommandTeamConfig commandTeamConfig,
            GHRepository repository, GHUser user) {
        try {
            if (commandPermissionConfig.getPermission() != null) {
                if (user == null) {
                    return false;
                }

                if (!repository.hasPermission(user, commandPermissionConfig.getPermission())) {
                    return false;
                }
            }
            if (!commandTeamConfig.getTeams().isEmpty()) {
                if (user == null) {
                    return false;
                }

                List matchingTeams = repository.getTeams().stream()
                        .filter(t -> commandTeamConfig.getTeams().contains(t.getSlug()))
                        .collect(Collectors.toList());

                for (GHTeam matchingTeam : matchingTeams) {
                    if (matchingTeam.hasMember(user)) {
                        return true;
                    }
                }

                return false;
            }

            return true;
        } catch (IOException e) {
            throw new CommandExecutionException("Unable to check the permissions for the command", e);
        }
    }

    private CommandConfig getBestCommandConfigInErrorState(ParseResult parseResult) {
        if (parseResult.getState().getCommand() == null) {
            return cliConfig.getDefaultCommandConfig();
        }

        return commandConfigs.getOrDefault(parseResult.getState().getCommand().getType().getName(),
                cliConfig.getDefaultCommandConfig());
    }

    private void handleParseError(IssueComment issueCommentPayload, String command,
            ParseResult parseResult, String error) {
        if (!cliConfig.getParseErrorStrategy().addMessage()) {
            return;
        }

        StringBuilder message = new StringBuilder();
        message.append(String.format(cliConfig.getParseErrorMessage(), command));

        if (cliConfig.getParseErrorStrategy().includeErrors()) {
            message.append("\n\nErrors:\n");
            if (error != null) {
                message.append("\n- " + error);
            }
            if (parseResult != null) {
                for (ParseException parseError : parseResult.getErrors()) {
                    message.append("\n- " + parseError.getMessage());
                }
            }
        }

        if (error == null && cliConfig.getParseErrorStrategy().includeHelp()) {
            try {
                ByteArrayOutputStream helpOs = new ByteArrayOutputStream();

                if (parseResult != null && parseResult.getState().getCommand() != null) {
                    Help.help(parseResult.getState().getCommand(), helpOs);
                } else {
                    Help.help(cli.getMetadata(), Collections.emptyList(), helpOs);
                }

                String help = helpOs.toString(StandardCharsets.UTF_8);

                if (!help.isBlank()) {
                    message.append("\n\nHelp:\n\n").append("```\n" + help.trim() + "\n```");
                }
            } catch (IOException e) {
                LOGGER.warn("Error trying to generate help for command `" + command + "` in "
                        + issueCommentPayload.getRepository().getFullName() + "#" + issueCommentPayload.getIssue().getNumber(),
                        e);
            }
        }

        try {
            issueCommentPayload.getIssue().comment(message.toString());
        } catch (Exception e) {
            LOGGER.warn("Error trying to add command parse error comment for command `" + command + "` in "
                    + issueCommentPayload.getRepository().getFullName() + "#" + issueCommentPayload.getIssue().getNumber(), e);
        }
    }

    protected void handleExecutionError(GHEventPayload.IssueComment issueCommentPayload,
            CommandExecutionContext commandExecutionContext) {
        CommandConfig commandConfig = commandExecutionContext.getCommandConfig();
        String commandLine = commandExecutionContext.getCommandLine();

        if (!commandConfig.getExecutionErrorStrategy().addMessage()) {
            return;
        }
        try {
            issueCommentPayload.getIssue()
                    .comment(String.format(commandConfig.getExecutionErrorMessage(), commandLine));
        } catch (Exception e) {
            LOGGER.warn("Error trying to add command execution error comment for command `" + commandLine + "` in "
                    + issueCommentPayload.getRepository().getFullName() + "#" + issueCommentPayload.getIssue().getNumber(), e);
        }
    }

    public static class CommandExecutionContext {

        private final String commandLine;

        private final C command;

        private final GHReaction ackReaction;

        private final CommandConfig commandConfig;

        public CommandExecutionContext(String commandLine, C command, CommandConfig commandConfig, GHReaction ackReaction) {
            this.commandLine = commandLine;
            this.command = command;
            this.commandConfig = commandConfig;
            this.ackReaction = ackReaction;
        }

        public String getCommandLine() {
            return commandLine;
        }

        public C getCommand() {
            return command;
        }

        public CommandConfig getCommandConfig() {
            return commandConfig;
        }

        public GHReaction getAckReaction() {
            return ackReaction;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy