co.cask.cdap.cli.command.system.HelpCommand.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.cli.command.system.HelpCommand.java

Source

/*
 * Copyright  2012-2014 Cask Data, Inc.
 *
 * 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 co.cask.cdap.cli.command.system;

import co.cask.cdap.cli.ArgumentName;
import co.cask.cdap.cli.Categorized;
import co.cask.cdap.cli.CommandCategory;
import co.cask.cdap.cli.util.StringStyler;
import co.cask.cdap.cli.util.table.TableRendererConfig;
import co.cask.common.cli.Arguments;
import co.cask.common.cli.Command;
import co.cask.common.cli.CommandSet;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

import java.io.PrintStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Prints helper text for all commands.
 */
public class HelpCommand implements Command {

    protected final Supplier<Iterable<CommandSet<Command>>> commands;

    private final TableRendererConfig tableRendererConfig;

    public HelpCommand(Supplier<Iterable<CommandSet<Command>>> commands, TableRendererConfig tableRendererConfig) {
        this.commands = commands;
        this.tableRendererConfig = tableRendererConfig;
    }

    @Override
    public void execute(Arguments arguments, PrintStream output) throws Exception {
        output.println();
        output.println();

        if (arguments.hasArgument(ArgumentName.COMMAND_CATEGORY.getName())) {
            // help with one category
            Multimap<String, Command> categorizedCommands = categorizeCommands(commands.get(),
                    CommandCategory.GENERAL, Predicates.<Command>alwaysTrue());
            String commandCategoryInput = arguments.get(ArgumentName.COMMAND_CATEGORY.getName());
            CommandCategory category = CommandCategory.valueOfNameIgnoreCase(commandCategoryInput);

            List<Command> commandList = Lists.newArrayList(categorizedCommands.get(category.getName()));
            if (commandList.isEmpty()) {
                output.printf("No commands found in the %s category", category.getName());
                output.println();
                return;
            }

            printCommands(output, category.getName(), commandList);
        } else {
            // normal help

            Multimap<String, Command> categorizedCommands = categorizeCommands(commands.get(),
                    CommandCategory.GENERAL, Predicates.<Command>alwaysTrue());
            for (CommandCategory category : CommandCategory.values()) {
                List<Command> commandList = Lists.newArrayList(categorizedCommands.get(category.getName()));
                if (commandList.isEmpty()) {
                    continue;
                }

                printCommands(output, category.getName(), commandList);
            }
        }
    }

    protected void printCommands(PrintStream output, String category, List<Command> commandList) {
        Collections.sort(commandList, new Comparator<Command>() {
            @Override
            public int compare(Command command, Command command2) {
                return command.getPattern().compareTo(command2.getPattern());
            }
        });

        output.println(StringStyler.bold(category));
        output.println();

        for (Command command : commandList) {
            printPattern(command.getPattern(), output, tableRendererConfig.getLineWidth(), 2);
            wrappedPrint(command.getDescription(), output, tableRendererConfig.getLineWidth(), 4);
            output.println();
        }
    }

    protected Multimap<String, Command> categorizeCommands(Iterable<CommandSet<Command>> commandSets,
            CommandCategory defaultCategory, Predicate<Command> filter) {
        Multimap<String, Command> result = HashMultimap.create();
        for (CommandSet<Command> commandSet : commandSets) {
            populate(result, commandSet, getCategory(commandSet), defaultCategory, filter);
        }
        return result;
    }

    /**
     * Recursive helper for {@link #categorizeCommands(Iterable, CommandCategory, Predicate)}.
     */
    private void populate(Multimap<String, Command> result, CommandSet<Command> commandSet,
            Optional<String> parentCategory, CommandCategory defaultCategory, Predicate<Command> filter) {

        for (Command childCommand : Iterables.filter(commandSet.getCommands(), filter)) {
            Optional<String> commandCategory = getCategory(childCommand).or(parentCategory);
            result.put(commandCategory.or(defaultCategory.getName()), childCommand);
        }

        for (CommandSet<Command> childCommandSet : commandSet.getCommandSets()) {
            Optional<String> commandCategory = getCategory(childCommandSet).or(parentCategory);
            populate(result, childCommandSet, commandCategory, defaultCategory, filter);
        }
    }

    private Optional<String> getCategory(Object object) {
        if (object instanceof Categorized) {
            return Optional.of(((Categorized) object).getCategory());
        }
        return Optional.absent();
    }

    @Override
    public String getPattern() {
        return String.format("help [<%s>]", ArgumentName.COMMAND_CATEGORY);
    }

    @Override
    public String getDescription() {
        return String.format("Prints this helper text. Optionally, provide <%s> for help for a specific category.",
                ArgumentName.COMMAND_CATEGORY);
    }

    /**
     * Prints the given command pattern with text wrapping at the given column width. It prints multiple lines as:
     *
     * <pre>{@code
     * command sub-command <arg1> <arg2>
     *                     <arg3> [<optional-long-arg4>]
     * }
     * </pre>
     *
     * @param pattern the command pattern to print
     * @param output the {@link PrintStream} to write to
     * @param colWidth width of the column
     * @param prefixSpaces number of spaces as the prefix for each line printed
     */
    private void printPattern(String pattern, PrintStream output, int colWidth, int prefixSpaces) {
        String prefix = Strings.repeat(" ", prefixSpaces);
        colWidth -= prefixSpaces;

        if (pattern.length() <= colWidth) {
            output.printf("%s%s", prefix, pattern);
            output.println();
            return;
        }

        // Find the first <argument>, it is used for alignment in second line and onward.
        int startIdx = pattern.indexOf('<');
        // If no '<', it shouldn't reach here (it should be a short command). If it does, just print it.
        if (startIdx < 0) {
            output.printf("%s%s", prefix, pattern);
            output.println();
            return;
        }

        // First line should at least include the first <argument>
        int idx = pattern.lastIndexOf('>', colWidth) + 1;
        if (idx <= 0) {
            idx = pattern.indexOf('>', startIdx) + 1;
            if (idx <= 0) {
                idx = pattern.length();
            }
        }
        // Make sure we include the closing ] of an optional argument
        if (idx < pattern.length() && pattern.charAt(idx) == ']') {
            idx++;
        }
        output.printf("%s%s", prefix, pattern.substring(0, idx));
        output.println();

        if ((idx + 1) < pattern.length()) {
            // For rest of the line, align them to the startIdx
            wrappedPrint(pattern.substring(idx + 1), output, colWidth, startIdx + prefixSpaces);
        }
    }

    /**
     * Prints the given string with text wrapping at the given column width.
     *
     * @param str the string to print
     * @param output the {@link PrintStream} to write to
     * @param colWidth width of the column
     * @param prefixSpaces number of spaces as the prefix for each line printed
     */
    private void wrappedPrint(String str, PrintStream output, int colWidth, int prefixSpaces) {
        String prefix = Strings.repeat(" ", prefixSpaces);
        colWidth -= prefixSpaces;

        if (str.length() <= colWidth) {
            output.printf("%s%s", prefix, str);
            output.println();
            return;
        }

        int beginIdx = 0;
        while (beginIdx < str.length()) {
            int idx;
            if (beginIdx + colWidth >= str.length()) {
                idx = str.length();
            } else {
                idx = str.lastIndexOf(' ', beginIdx + colWidth);
            }

            // Cannot break line if no space found between beginIdx and (beginIdx + colWidth)
            // The best we can do is to look forward.
            // The line will be longer than colWidth though.
            if (idx < 0 || idx < beginIdx) {
                idx = str.indexOf(' ', beginIdx + colWidth);
                if (idx < 0) {
                    idx = str.length();
                }
            }
            output.printf("%s%s", prefix, str.substring(beginIdx, idx));
            beginIdx = idx + 1;
            output.println();
        }
    }
}