/* * Copyright (c) 2009-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.planet57.gshell.commands.standard; import java.util.Collection; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.inject.Inject; import com.planet57.gshell.command.Command; import com.planet57.gshell.command.CommandContext; import com.planet57.gshell.util.io.IO; import com.planet57.gshell.command.CommandActionSupport; import com.planet57.gshell.help.AliasHelpPage; import com.planet57.gshell.help.CommandHelpPage; import com.planet57.gshell.help.GroupHelpPage; import com.planet57.gshell.help.HelpPage; import com.planet57.gshell.help.HelpPageManager; import com.planet57.gshell.help.HelpPageUtil; import com.planet57.gshell.help.MetaHelpPage; import com.planet57.gshell.util.cli2.Argument; import com.planet57.gshell.util.cli2.Option; import com.planet57.gshell.util.jline.Complete; import com.planet57.gshell.util.pref.Preference; import com.planet57.gshell.util.pref.Preferences; import com.planet57.gshell.util.i18n.I18N; import com.planet57.gshell.util.i18n.MessageBundle; import static com.google.common.base.Preconditions.checkNotNull; /** * Display help pages. * * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @since 2.5 */ @Command(name = "help", description = "Display help pages") @Preferences(path = "commands/help") public class HelpAction extends CommandActionSupport { private interface Messages extends MessageBundle { @DefaultMessage("Available pages:") String availablePages(); @DefaultMessage("Matching pages:") String matchingPages(); @DefaultMessage("No help page available for @|bold %s|@. Try @|bold help|@ for a list of available pages.") String helpNotFound(String page); } private static final Messages messages = I18N.create(Messages.class); private final HelpPageManager helpPages; // TODO: maybe use an enum here to say; --include groups,commands,aliases (exclude meta) etc... @Preference @Option(name = "c", longName = "include-commands", description = "Include command pages", optionalArg = true) private Boolean includeCommands = true; @Preference @Option(name = "a", longName = "include-aliases", description = "Include alias pages", optionalArg = true) private Boolean includeAliases = true; @Preference @Option(name = "g", longName = "include-groups", description = "Include group pages", optionalArg = true) private Boolean includeGroups = true; @Preference @Option(name = "m", longName = "include-meta", description = "Include meta pages", optionalArg = true) private Boolean includeMeta = true; @Preference @Option(name = "A", longName = "include-all", description = "Include all pages", optionalArg = true) private Boolean includeAll; @Argument(description = "Display the help page for NAME or list pages matching NAME", token = "NAME") @Complete("help-page-name") private String name; @Inject public HelpAction(final HelpPageManager helpPages) { this.helpPages = checkNotNull(helpPages); } @Override public Object execute(@Nonnull final CommandContext context) throws Exception { IO io = context.getIo(); // If there is no argument given, display all help pages in context if (name == null) { displayAvailable(context); return null; } // First try a direct match HelpPage page = helpPages.getPage(name); // if not direct match, then look for similar pages if (page == null) { Collection<HelpPage> pages = helpPages.getPages(query( it -> it != null && (it.getName().contains(name) || it.getDescription().contains(name)) )); if (pages.size() == 1) { // if there is only one match, treat as a direct match page = pages.iterator().next(); } else if (pages.size() > 1) { // else show matching pages io.println(messages.matchingPages()); HelpPageUtil.renderIndex(io.out, pages); return null; } } // if not page matched, complain if (page == null) { io.println(messages.helpNotFound(name)); return 1; } page.render(context.getShell(), io.out); return null; } private void displayAvailable(final CommandContext context) { Collection<HelpPage> pages = helpPages.getPages(query(helpPage -> true)); IO io = context.getIo(); io.println(messages.availablePages()); HelpPageUtil.renderIndex(io.out, pages); } // FIXME: this and the --include-all doesn't seem to be very happy, redefine one one queries different types of pages private Predicate<HelpPage> query(final Predicate<HelpPage> predicate) { Predicate<HelpPage> query = predicate; if (includeAll == null || !includeAll) { if (includeAliases != null && !includeAliases) { query = query.and(TypePredicate.of(AliasHelpPage.class).negate()); } if (includeMeta != null && !includeMeta) { query = query.and(TypePredicate.of(MetaHelpPage.class).negate()); } if (includeCommands != null && !includeCommands) { query = query.and(TypePredicate.of(CommandHelpPage.class).negate()); } if (includeGroups != null && !includeGroups) { query = query.and(TypePredicate.of(GroupHelpPage.class).negate()); } } return query; } // // TypePredicate // private static class TypePredicate<T> implements Predicate<T> { private final Class<?> type; private TypePredicate(final Class<?> type) { this.type = checkNotNull(type); } @Override public boolean test(final T value) { return value != null && type.isAssignableFrom(value.getClass()); } public static <T> Predicate<T> of(final Class<?> type) { return new TypePredicate<>(type); } } }