    001/*
    002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
    003 *
    004 *  Permission is hereby granted, free of charge, to any person obtaining
    005 *  a copy of this software and associated documentation files (the
    006 *  "Software"), to deal in the Software without restriction, including
    007 *  without limitation the rights to use, copy, modify, merge, publish,
    008 *  distribute, sublicense, and/or sell copies of the Software, and to
    009 *  permit persons to whom the Software is furnished to do so, subject to
    010 *  the following conditions:
    011 *
    012 *  The above copyright notice and this permission notice shall be
    013 *  included in all copies or substantial portions of the Software.
    014 *
    015 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    016 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    017 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    018 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    019 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    020 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    021 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    022 */
    023
    024package co.aikar.commands;
    025
    026import com.google.common.collect.SetMultimap;
    027
    028import java.util.ArrayList;
    029import java.util.Comparator;
    030import java.util.HashSet;
    031import java.util.Iterator;
    032import java.util.List;
    033import java.util.Set;
    034import java.util.regex.Pattern;
    035
    036@SuppressWarnings("WeakerAccess")
    037public class CommandHelp {
    038    private final CommandManager manager;
    039    private final CommandIssuer issuer;
    040    private final List<HelpEntry> helpEntries = new ArrayList<>();
    041    private final String commandName;
    042    final String commandPrefix;
    043    private int page;
    044    private int perPage;
    045    List<String> search;
    046    private HelpEntry selectedEntry;
    047    private int totalResults;
    048    private int totalPages;
    049    private boolean lastPage;
    050
    051    public CommandHelp(CommandManager manager, RootCommand rootCommand, CommandIssuer issuer) {
    052        this.manager = manager;
    053        this.issuer = issuer;
    054        this.perPage = manager.defaultHelpPerPage;
    055        this.commandPrefix = manager.getCommandPrefix(issuer);
    056        this.commandName = rootCommand.getCommandName();
    057
    058
    059        SetMultimap<String, RegisteredCommand> subCommands = rootCommand.getSubCommands();
    060        Set<RegisteredCommand> seen = new HashSet<>();
    061
    062        if (!rootCommand.getDefCommand().hasHelpCommand) {
    063            helpEntries.add(new HelpEntry(this, rootCommand.getDefaultRegisteredCommand()));
    064            seen.add(rootCommand.getDefaultRegisteredCommand());
    065        }
    066
    067        subCommands.entries().forEach(e -> {
    068            String key = e.getKey();
    069            if (key.equals(BaseCommand.DEFAULT) || key.equals(BaseCommand.CATCHUNKNOWN)) {
    070                return;
    071            }
    072
    073            RegisteredCommand regCommand = e.getValue();
    074
    075            if (!regCommand.isPrivate && regCommand.hasPermission(issuer) && !seen.contains(regCommand)) {
    076                this.helpEntries.add(new HelpEntry(this, regCommand));
    077                seen.add(regCommand);
    078            }
    079        });
    080    }
    081
    082    @UnstableAPI // Not sure on this one yet even when API becomes unstable
    083    protected void updateSearchScore(HelpEntry help) {
    084        if (this.search == null || this.search.isEmpty()) {
    085            help.setSearchScore(1);
    086            return;
    087        }
    088        final RegisteredCommand<?> cmd = help.getRegisteredCommand();
    089
    090        int searchScore = 0;
    091        for (String word : this.search) {
    092            Pattern pattern = Pattern.compile(".*" + Pattern.quote(word) + ".*", Pattern.CASE_INSENSITIVE);
    093            for (String subCmd : cmd.registeredSubcommands) {
    094                Pattern subCmdPattern = Pattern.compile(".*" + Pattern.quote(subCmd) + ".*", Pattern.CASE_INSENSITIVE);
    095                if (pattern.matcher(subCmd).matches()) {
    096                    searchScore += 3;
    097                } else if (subCmdPattern.matcher(word).matches()) {
    098                    searchScore++;
    099                }
    100            }
    101
    102
    103            if (pattern.matcher(help.getDescription()).matches()) {
    104                searchScore += 2;
    105            }
    106            if (pattern.matcher(help.getParameterSyntax()).matches()) {
    107                searchScore++;
    108            }
    109            if (help.getSearchTags() != null && pattern.matcher(help.getSearchTags()).matches()) {
    110                searchScore += 2;
    111            }
    112        }
    113        help.setSearchScore(searchScore);
    114    }
    115
    116    public CommandManager getManager() {
    117        return manager;
    118    }
    119
    120    public boolean isExactMatch(String command) {
    121        for (HelpEntry helpEntry : helpEntries) {
    122            if (helpEntry.getCommand().endsWith(" " + command)) {
    123                selectedEntry = helpEntry;
    124                return true;
    125            }
    126        }
    127        return false;
    128    }
    129
    130    public void showHelp() {
    131        showHelp(issuer);
    132    }
    133
    134    public void showHelp(CommandIssuer issuer) {
    135        CommandHelpFormatter formatter = manager.getHelpFormatter();
    136        if (selectedEntry != null) {
    137            formatter.showDetailedHelp(this, selectedEntry);
    138            return;
    139        }
    140
    141        List<HelpEntry> helpEntries = getHelpEntries();
    142        Iterator<HelpEntry> results = helpEntries.stream()
    143                .filter(HelpEntry::shouldShow)
    144                .sorted(Comparator.comparingInt(helpEntry -> helpEntry.getSearchScore() * -1)).iterator();
    145        if (!results.hasNext()) {
    146            issuer.sendMessage(MessageType.ERROR, MessageKeys.NO_COMMAND_MATCHED_SEARCH, "{search}", ACFUtil.join(this.search, " "));
    147            helpEntries = getHelpEntries();
    148            results = helpEntries.iterator();
    149        }
    150        this.totalResults = helpEntries.size();
    151        int min = (this.page - 1) * this.perPage; // TODO: per page configurable?
    152        int max = min + this.perPage;
    153        this.totalPages = (int) Math.ceil((float) totalResults / (float) this.perPage);
    154        int i = 0;
    155        if (min >= totalResults) {
    156            issuer.sendMessage(MessageType.HELP, MessageKeys.HELP_NO_RESULTS);
    157            return;
    158        }
    159
    160        List<HelpEntry> printEntries = new ArrayList<>();
    161        while (results.hasNext()) {
    162            HelpEntry e = results.next();
    163            if (i >= max) {
    164                break;
    165            }
    166            if (i++ < min) {
    167                continue;
    168            }
    169            printEntries.add(e);
    170        }
    171        this.lastPage = max >= totalResults;
    172
    173        if (search == null) {
    174            formatter.showAllResults(this, printEntries);
    175        } else {
    176            formatter.showSearchResults(this, printEntries);
    177        }
    178
    179    }
    180
    181    public List<HelpEntry> getHelpEntries() {
    182        return helpEntries;
    183    }
    184
    185    public void setPerPage(int perPage) {
    186        this.perPage = perPage;
    187    }
    188
    189    public void setPage(int page) {
    190        this.page = page;
    191    }
    192
    193    public void setPage(int page, int perPage) {
    194        this.setPage(page);
    195        this.setPerPage(perPage);
    196    }
    197
    198    public void setSearch(List<String> search) {
    199        this.search = search;
    200        getHelpEntries().forEach(this::updateSearchScore);
    201    }
    202
    203    public CommandIssuer getIssuer() {
    204        return issuer;
    205    }
    206
    207    public String getCommandName() {
    208        return commandName;
    209    }
    210
    211    public String getCommandPrefix() {
    212        return commandPrefix;
    213    }
    214
    215    public int getPage() {
    216        return page;
    217    }
    218
    219    public int getPerPage() {
    220        return perPage;
    221    }
    222
    223    public List<String> getSearch() {
    224        return search;
    225    }
    226
    227    public HelpEntry getSelectedEntry() {
    228        return selectedEntry;
    229    }
    230
    231    public int getTotalResults() {
    232        return totalResults;
    233    }
    234
    235    public int getTotalPages() {
    236        return totalPages;
    237    }
    238
    239    public boolean isLastPage() {
    240        return lastPage;
    241    }
    242}