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; 035import java.util.stream.Collectors; 036 037@SuppressWarnings("WeakerAccess") 038public class CommandHelp { 039 private final CommandManager manager; 040 private final CommandIssuer issuer; 041 private final List<HelpEntry> helpEntries = new ArrayList<>(); 042 private final String commandName; 043 final String commandPrefix; 044 private int page = 1; 045 private int perPage; 046 List<String> search; 047 private Set<HelpEntry> selectedEntry = new HashSet<>(); 048 private int totalResults; 049 private int totalPages; 050 private boolean lastPage; 051 052 public CommandHelp(CommandManager manager, RootCommand rootCommand, CommandIssuer issuer) { 053 this.manager = manager; 054 this.issuer = issuer; 055 this.perPage = manager.defaultHelpPerPage; 056 this.commandPrefix = manager.getCommandPrefix(issuer); 057 this.commandName = rootCommand.getCommandName(); 058 059 060 SetMultimap<String, RegisteredCommand> subCommands = rootCommand.getSubCommands(); 061 Set<RegisteredCommand> seen = new HashSet<>(); 062 063 if (!rootCommand.getDefCommand().hasHelpCommand) { 064 RegisteredCommand defCommand = rootCommand.getDefaultRegisteredCommand(); 065 if (defCommand != null) { 066 helpEntries.add(new HelpEntry(this, defCommand)); 067 seen.add(defCommand); 068 } 069 } 070 071 subCommands.entries().forEach(e -> { 072 String key = e.getKey(); 073 if (key.equals(BaseCommand.DEFAULT) || key.equals(BaseCommand.CATCHUNKNOWN)) { 074 return; 075 } 076 077 RegisteredCommand regCommand = e.getValue(); 078 079 if (!regCommand.isPrivate && regCommand.hasPermission(issuer) && !seen.contains(regCommand)) { 080 this.helpEntries.add(new HelpEntry(this, regCommand)); 081 seen.add(regCommand); 082 } 083 }); 084 } 085 086 @UnstableAPI // Not sure on this one yet even when API becomes unstable 087 protected void updateSearchScore(HelpEntry help) { 088 if (this.search == null || this.search.isEmpty()) { 089 help.setSearchScore(1); 090 return; 091 } 092 final RegisteredCommand<?> cmd = help.getRegisteredCommand(); 093 094 int searchScore = 0; 095 for (String word : this.search) { 096 Pattern pattern = Pattern.compile(".*" + Pattern.quote(word) + ".*", Pattern.CASE_INSENSITIVE); 097 for (String subCmd : cmd.registeredSubcommands) { 098 Pattern subCmdPattern = Pattern.compile(".*" + Pattern.quote(subCmd) + ".*", Pattern.CASE_INSENSITIVE); 099 if (pattern.matcher(subCmd).matches()) { 100 searchScore += 3; 101 } else if (subCmdPattern.matcher(word).matches()) { 102 searchScore++; 103 } 104 } 105 106 107 if (pattern.matcher(help.getDescription()).matches()) { 108 searchScore += 2; 109 } 110 if (pattern.matcher(help.getParameterSyntax(issuer)).matches()) { 111 searchScore++; 112 } 113 if (help.getSearchTags() != null && pattern.matcher(help.getSearchTags()).matches()) { 114 searchScore += 2; 115 } 116 } 117 help.setSearchScore(searchScore); 118 } 119 120 public CommandManager getManager() { 121 return manager; 122 } 123 124 public boolean testExactMatch(String command) { 125 selectedEntry.clear(); 126 for (HelpEntry helpEntry : helpEntries) { 127 if (helpEntry.getCommand().endsWith(" " + command)) { 128 selectedEntry.add(helpEntry); 129 } 130 } 131 return !selectedEntry.isEmpty(); 132 } 133 134 public void showHelp() { 135 showHelp(issuer); 136 } 137 138 public void showHelp(CommandIssuer issuer) { 139 CommandHelpFormatter formatter = manager.getHelpFormatter(); 140 if (!selectedEntry.isEmpty()) { 141 HelpEntry first = ACFUtil.getFirstElement(selectedEntry); 142 formatter.printDetailedHelpHeader(this, issuer, first); 143 144 for (HelpEntry helpEntry : selectedEntry) { 145 formatter.showDetailedHelp(this, helpEntry); 146 } 147 148 formatter.printDetailedHelpFooter(this, issuer, first); 149 return; 150 } 151 152 List<HelpEntry> helpEntries = getHelpEntries().stream().filter(HelpEntry::shouldShow).collect(Collectors.toList()); 153 Iterator<HelpEntry> results = helpEntries.stream() 154 .sorted(Comparator.comparingInt(helpEntry -> helpEntry.getSearchScore() * -1)).iterator(); 155 if (!results.hasNext()) { 156 issuer.sendMessage(MessageType.ERROR, MessageKeys.NO_COMMAND_MATCHED_SEARCH, "{search}", ACFUtil.join(this.search, " ")); 157 helpEntries = getHelpEntries(); 158 results = helpEntries.iterator(); 159 } 160 this.totalResults = helpEntries.size(); 161 int min = (this.page - 1) * this.perPage; // TODO: per page configurable? 162 int max = min + this.perPage; 163 this.totalPages = (int) Math.ceil((float) totalResults / (float) this.perPage); 164 int i = 0; 165 if (min >= totalResults) { 166 issuer.sendMessage(MessageType.HELP, MessageKeys.HELP_NO_RESULTS); 167 return; 168 } 169 170 List<HelpEntry> printEntries = new ArrayList<>(); 171 while (results.hasNext()) { 172 HelpEntry e = results.next(); 173 if (i >= max) { 174 break; 175 } 176 if (i++ < min) { 177 continue; 178 } 179 printEntries.add(e); 180 } 181 this.lastPage = max >= totalResults; 182 183 if (search == null) { 184 formatter.showAllResults(this, printEntries); 185 } else { 186 formatter.showSearchResults(this, printEntries); 187 } 188 189 } 190 191 public List<HelpEntry> getHelpEntries() { 192 return helpEntries; 193 } 194 195 public void setPerPage(int perPage) { 196 this.perPage = perPage; 197 } 198 199 public void setPage(int page) { 200 this.page = page; 201 } 202 203 public void setPage(int page, int perPage) { 204 this.setPage(page); 205 this.setPerPage(perPage); 206 } 207 208 public void setSearch(List<String> search) { 209 this.search = search; 210 getHelpEntries().forEach(this::updateSearchScore); 211 } 212 213 public CommandIssuer getIssuer() { 214 return issuer; 215 } 216 217 public String getCommandName() { 218 return commandName; 219 } 220 221 public String getCommandPrefix() { 222 return commandPrefix; 223 } 224 225 public int getPage() { 226 return page; 227 } 228 229 public int getPerPage() { 230 return perPage; 231 } 232 233 public List<String> getSearch() { 234 return search; 235 } 236 237 public Set<HelpEntry> getSelectedEntry() { 238 return selectedEntry; 239 } 240 241 public int getTotalResults() { 242 return totalResults; 243 } 244 245 public int getTotalPages() { 246 return totalPages; 247 } 248 249 public boolean isOnlyPage() { 250 return this.page == 1 && lastPage; 251 } 252 253 public boolean isLastPage() { 254 return lastPage; 255 } 256}