netudima commented on code in PR #2497: URL: https://github.com/apache/cassandra/pull/2497#discussion_r2106314264
########## src/java/org/apache/cassandra/tools/nodetool/layout/CassandraCliHelpLayout.java: ########## @@ -0,0 +1,774 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.tools.nodetool.layout; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.tools.nodetool.CommandUtils; +import org.apache.cassandra.tools.nodetool.JmxConnect; +import org.apache.cassandra.tools.nodetool.TopLevelCommand; +import org.apache.cassandra.utils.Pair; +import picocli.CommandLine; + +import static org.apache.cassandra.tools.nodetool.CommandUtils.findCassandraBackwardCompatibleArgument; +import static org.apache.cassandra.tools.nodetool.CommandUtils.leadingSpaces; +import static org.apache.cassandra.tools.nodetool.CommandUtils.sortShortestFirst; +import static org.apache.commons.lang3.ArrayUtils.isEmpty; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_END_OF_OPTIONS; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_HEADER; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_HEADER_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_PARAMETER_LIST; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_PARAMETER_LIST_HEADING; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS_HEADING; + +/** + * Help factory for the Cassandra nodetool to generate the help output in the format + * of the airline help output, which is used as the default layout for the Cassandra nodetool. + * <p> + * Note, that JMX connect options are always shown in the help output and are not hidden. The + * {@link JmxConnect} class is used to connect to a C* node via JMX, but the opttions are not + * part of the command hierarchy to allow reusage of the commands in other contexts. + */ +public class CassandraCliHelpLayout extends CommandLine.Help +{ + // The default width for the usage help output to match the width of + // the airline help output and minimize the divergence of layouts. + public static final int DEFAULT_USAGE_HELP_WIDTH = 88; + private static final int TOP_LEVEL_USAGE_HELP_WIDTH = 999; + private static final String DESCRIPTION_HEADING = "NAME%n"; + private static final String SYNOPSIS_HEADING = "SYNOPSIS%n"; + private static final String OPTIONS_HEADING = "OPTIONS%n"; + private static final String COMMANDS_HEADING = "COMMANDS%n"; + private static final String FOOTER_HEADING = "%n"; + private static final int DESCRIPTION_INDENT = 4; + public static final int COLUMN_INDENT = 8; + public static final int SUBCOMMANDS_INDENT = 4; + public static final int SUBCOMMANDS_DESCRIPTION_INDENT_TOP_LEVEL = 3; + private static final CommandLine.Model.OptionSpec CASSANDRA_END_OF_OPTIONS_OPTION = + CommandLine.Model.OptionSpec.builder("--") + .description("This option can be used to separate command-line options from the " + + "list of argument, (useful when arguments might be mistaken for " + + "command-line options") + .arity("0") + .build(); + public static final String TOP_LEVEL_SYNOPSIS_LIST_PREFIX = "usage:"; + public static final String TOP_LEVEL_COMMAND_HEADING = "The most commonly used nodetool commands are:"; + public static final String USAGE_HELP_FOOTER = "See 'nodetool help <command>' for more information on a specific command."; + public static final String SYNOPSIS_SUBCOMMANDS_LABEL = "<command> [<args>]"; + public static final String SUBCOMMAND_OPTION_TEMPLATE = "With %s option, %s"; + public static final String SUBCOMMAND_SUBHEADER = "With no arguments, Display help information"; + private static final String[] EMPTY_FOOTER = new String[0]; + + public CassandraCliHelpLayout(CommandLine.Model.CommandSpec spec, ColorScheme scheme) + { + super(spec, scheme); + } + + @Override + public String descriptionHeading(Object... params) + { + return createHeading(DESCRIPTION_HEADING, params); + } + + /** + * @param params Arguments referenced by the format specifiers in the header strings + * @return the header string. + */ + @Override + public String description(Object... params) + { + CommandLine.Model.CommandSpec spec = commandSpec(); + String fullName = spec.qualifiedName(); + + TextTable table = TextTable.forColumns(colorScheme(), + new Column(spec.usageMessage().width() - COLUMN_INDENT, COLUMN_INDENT, + Column.Overflow.WRAP)); + table.setAdjustLineBreaksForWideCJKCharacters(spec.usageMessage().adjustLineBreaksForWideCJKCharacters()); + table.indentWrappedLines = 0; + + table.addRowValues(colorScheme().commandText(fullName) + .concat(" - ") + .concat(colorScheme().text(String.join(" ", spec.usageMessage().description())))); + table.addRowValues(Ansi.OFF.new Text("", colorScheme())); + return table.toString(new StringBuilder()).toString(); + } + + @Override + public String synopsisHeading(Object... params) + { + return createHeading(SYNOPSIS_HEADING, params); + } + + /** + * This method is overridden to provide a detailed synopsis for the command and its subcommands. + * <pre> + * {@code + * SYNOPSIS + * nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)] + * [(-pp | --print-port)] [(-pw <password> | --password <password>)] + * [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)] + * [(-u <username> | --username <username>)] bootstrap <command> + * [<args>] + * } + * </pre> + * + * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line + * @return The synopsis string. + */ + @Override + public String synopsis(int synopsisHeadingLength) + { + return printDetailedSynopsis("", COLUMN_INDENT, true); + } + + private String printDetailedSynopsis(String synopsisPrefix, int columnIndent, boolean showEndOfOptionsDelimiter) + { + StringBuilder top = new StringBuilder(printDetailedSynopsis(commandSpec(), synopsisPrefix, columnIndent, showEndOfOptionsDelimiter)); + for (CommandLine sub : commandSpec().subcommands().values()) + top.append(printDetailedSynopsis(sub.getCommandSpec(), synopsisPrefix, columnIndent, showEndOfOptionsDelimiter)); + return top.toString(); + } + + private String printDetailedSynopsis(CommandLine.Model.CommandSpec commandSpec, + String synopsisPrefix, + int columnIndent, + boolean showEndOfOptionsDelimiter) + { + // Cassandra uses end of options delimiter in usage help. + commandSpec.usageMessage().showEndOfOptionsDelimiterInUsageHelp(showEndOfOptionsDelimiter); + + ColorScheme colorScheme = colorScheme(); + + List<CommandLine.Model.OptionSpec> parentOptions = parentCommandOptionsWithJmxOptions(commandSpec); + List<CommandLine.Model.OptionSpec> commandOptions = commandSpec.options(); + // Retain only the options that are not part of the command hierarchy (e.g. dynamic options). + Comparator<CommandLine.Model.OptionSpec> comparator = new OptionSpecByNamesComparator(); + parentOptions.removeIf(o -> commandOptions.stream().anyMatch(c -> comparator.compare(o, c) == 0)); Review Comment: better names for o and c would make the code a bit more readable -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For additional commands, e-mail: pr-h...@cassandra.apache.org