Skip to content

Commit

Permalink
IGNITE-22777 Improve CLI usability (#4280)
Browse files Browse the repository at this point in the history
Not the usage text is more user-friendly
  • Loading branch information
PakhomovAlexander authored Aug 29, 2024
1 parent 1aead9b commit 35fbde0
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,52 @@

package org.apache.ignite.internal.cli.commands;

import static org.apache.ignite.internal.cli.commands.CommandConstants.ABBREVIATE_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.COMMAND_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.DESCRIPTION_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.HELP_OPTION_ORDER;
import static org.apache.ignite.internal.cli.commands.CommandConstants.OPTION_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.PARAMETER_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.REQUIRED_OPTION_MARKER;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_OPTIONS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SYNOPSIS_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.USAGE_HELP_AUTO_WIDTH;
import static org.apache.ignite.internal.cli.commands.CommandConstants.VERBOSE_OPTION_ORDER;
import static org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION_DESC;
import static org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION_SHORT;
import static org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_DESC;
import static org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_SHORT;

import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Spec;

/**
* Base class for commands.
*/
@Command(
descriptionHeading = DESCRIPTION_HEADING,
optionListHeading = OPTION_LIST_HEADING,
synopsisHeading = SYNOPSIS_HEADING,
requiredOptionMarker = REQUIRED_OPTION_MARKER,
usageHelpAutoWidth = USAGE_HELP_AUTO_WIDTH,
sortOptions = SORT_OPTIONS,
sortSynopsis = SORT_SYNOPSIS,
abbreviateSynopsis = ABBREVIATE_SYNOPSIS,
commandListHeading = COMMAND_LIST_HEADING,
parameterListHeading = PARAMETER_LIST_HEADING
)
public abstract class BaseCommand {
/** Help option specification. */
@Option(names = {HELP_OPTION, HELP_OPTION_SHORT}, usageHelp = true, description = HELP_OPTION_DESC)
@Option(names = {HELP_OPTION, HELP_OPTION_SHORT}, description = HELP_OPTION_DESC, usageHelp = true, order = HELP_OPTION_ORDER)
protected boolean usageHelpRequested;

/** Verbose option specification. */
@Option(names = {VERBOSE_OPTION, VERBOSE_OPTION_SHORT}, description = VERBOSE_OPTION_DESC)
@Option(names = {VERBOSE_OPTION, VERBOSE_OPTION_SHORT}, description = VERBOSE_OPTION_DESC, order = VERBOSE_OPTION_ORDER)
protected boolean verbose;

/** Instance of picocli command specification. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.ignite.internal.cli.commands;

/**
* Constants for @Command annotation.
*/
public class CommandConstants {
public static final String DESCRIPTION_HEADING = "%nDESCRIPTION%n";
public static final String OPTION_LIST_HEADING = "%nOPTIONS @|fg(246) * - required option|@ %n";
public static final String SYNOPSIS_HEADING = "%nUSAGE%n";
public static final String COMMAND_LIST_HEADING = "%nCOMMANDS%n";
public static final String PARAMETER_LIST_HEADING = "%nPARAMETERS @|fg(246) * - required parameter|@ %n";
public static final char REQUIRED_OPTION_MARKER = '*';
public static final boolean USAGE_HELP_AUTO_WIDTH = true;
public static final boolean SORT_OPTIONS = false;
public static final boolean SORT_SYNOPSIS = false;
public static final boolean ABBREVIATE_SYNOPSIS = true;


public static final int CLUSTER_URL_OPTION_ORDER = 10;
public static final int PROFILE_OPTION_ORDER = 11;
public static final int HELP_OPTION_ORDER = 100;
public static final int VERBOSE_OPTION_ORDER = 101;
public static final int VERSION_OPTION_ORDER = 102;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.ignite.internal.cli.commands;

import static org.apache.ignite.internal.cli.commands.CommandConstants.PROFILE_OPTION_ORDER;
import static org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_OPTION_DESC;

Expand All @@ -26,7 +27,7 @@
* Mixin for profile option.
*/
public class ProfileMixin {
@Option(names = PROFILE_OPTION, description = PROFILE_OPTION_DESC)
@Option(names = PROFILE_OPTION, description = PROFILE_OPTION_DESC, order = PROFILE_OPTION_ORDER)
private String profileName;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.ignite.internal.cli.commands;

import static org.apache.ignite.internal.cli.commands.CommandConstants.VERSION_OPTION_ORDER;
import static org.apache.ignite.internal.cli.commands.Options.Constants.VERSION_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.VERSION_OPTION_DESC;

Expand Down Expand Up @@ -51,6 +52,6 @@
})
public class TopLevelCliCommand extends BaseCommand {
@SuppressWarnings("PMD.UnusedPrivateField")
@Option(names = VERSION_OPTION, versionHelp = true, description = VERSION_OPTION_DESC)
@Option(names = VERSION_OPTION, versionHelp = true, description = VERSION_OPTION_DESC, order = VERSION_OPTION_ORDER)
private boolean versionRequested;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@

package org.apache.ignite.internal.cli.commands;

import static org.apache.ignite.internal.cli.commands.CommandConstants.ABBREVIATE_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.COMMAND_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.DESCRIPTION_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.OPTION_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.PARAMETER_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.REQUIRED_OPTION_MARKER;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_OPTIONS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SYNOPSIS_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.USAGE_HELP_AUTO_WIDTH;

import org.apache.ignite.internal.cli.commands.cliconfig.CliReplCommand;
import org.apache.ignite.internal.cli.commands.cluster.ClusterReplCommand;
import org.apache.ignite.internal.cli.commands.connect.ConnectReplCommand;
Expand All @@ -26,12 +37,13 @@
import org.apache.ignite.internal.cli.commands.sql.SqlReplCommand;
import org.apache.ignite.internal.cli.commands.version.VersionCommand;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.shell.jline3.PicocliCommands;

/**
* Top-level command that just prints help.
*/
@CommandLine.Command(name = "",
@Command(name = "",
footer = {"", "Press Ctrl-D to exit."},
subcommands = {
SqlReplCommand.class,
Expand All @@ -45,6 +57,19 @@
NodeReplCommand.class,
ClusterReplCommand.class,
RecoveryReplCommand.class
})
},

descriptionHeading = DESCRIPTION_HEADING,
optionListHeading = OPTION_LIST_HEADING,
synopsisHeading = SYNOPSIS_HEADING,
requiredOptionMarker = REQUIRED_OPTION_MARKER,
usageHelpAutoWidth = USAGE_HELP_AUTO_WIDTH,
sortOptions = SORT_OPTIONS,
sortSynopsis = SORT_SYNOPSIS,
abbreviateSynopsis = ABBREVIATE_SYNOPSIS,
commandListHeading = COMMAND_LIST_HEADING,
parameterListHeading = PARAMETER_LIST_HEADING

)
public class TopLevelCliReplCommand {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.ignite.internal.cli.commands.cluster;

import static org.apache.ignite.internal.cli.commands.CommandConstants.CLUSTER_URL_OPTION_ORDER;
import static org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION_DESC;

Expand All @@ -29,7 +30,12 @@
*/
public class ClusterUrlMixin {
/** Cluster endpoint URL option. */
@Option(names = CLUSTER_URL_OPTION, description = CLUSTER_URL_OPTION_DESC, converter = UrlConverter.class)
@Option(
names = CLUSTER_URL_OPTION,
description = CLUSTER_URL_OPTION_DESC,
converter = UrlConverter.class,
order = CLUSTER_URL_OPTION_ORDER
)
private URL clusterUrl;

public String getClusterUrl() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
*/
@Command(name = "init", description = "Initializes an Ignite cluster")
public class ClusterInitCommand extends BaseCommand implements Callable<Integer> {
/** Cluster endpoint URL option. */
@Mixin
private ClusterUrlProfileMixin clusterUrl;
private ClusterInitOptions clusterInitOptions;

/** Cluster endpoint URL option. */
@Mixin
private ClusterInitOptions clusterInitOptions;
private ClusterUrlProfileMixin clusterUrl;

@Inject
private ClusterInitCall call;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
* Mixin class for cluster init command options.
*/
public class ClusterInitOptions {
/** Name of the cluster. */
@Option(names = CLUSTER_NAME_OPTION, required = true, description = CLUSTER_NAME_OPTION_DESC, order = 0)
private String clusterName;

/**
* List of names of the nodes (each represented by a separate command line argument) that will host the Meta Storage. If the
* "--cluster-management-group" parameter is omitted, the same nodes will also host the Cluster Management Group.
Expand All @@ -62,6 +66,7 @@ public class ClusterInitOptions {
description = META_STORAGE_NODE_NAME_OPTION_DESC,
split = ",",
paramLabel = META_STORAGE_NODE_NAME_PARAM_LABEL,
order = 1,
preprocessor = SingleOccurrenceMetastorageConsumer.class
)
private List<String> metaStorageNodes;
Expand All @@ -73,24 +78,22 @@ public class ClusterInitOptions {
description = CMG_NODE_NAME_OPTION_DESC,
split = ",",
paramLabel = CMG_NODE_NAME_PARAM_LABEL,
order = 2,
preprocessor = SingleOccurrenceClusterManagementConsumer.class
)
private List<String> cmgNodes = new ArrayList<>();

/** Name of the cluster. */
@Option(names = CLUSTER_NAME_OPTION, required = true, description = CLUSTER_NAME_OPTION_DESC)
private String clusterName;

@ArgGroup
private ClusterConfigOptions clusterConfigOptions;

private static class ClusterConfigOptions {
@Option(names = CLUSTER_CONFIG_OPTION, description = CLUSTER_CONFIG_OPTION_DESC)
@Option(names = CLUSTER_CONFIG_OPTION, order = 3, description = CLUSTER_CONFIG_OPTION_DESC)
private String config;

@Option(names = CLUSTER_CONFIG_FILE_OPTION,
description = CLUSTER_CONFIG_FILE_OPTION_DESC,
split = ",",
order = 4,
paramLabel = CLUSTER_CONFIG_FILE_PARAM_LABEL
)
private List<File> files;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@

package org.apache.ignite.internal.cli.commands.sql;

import static org.apache.ignite.internal.cli.commands.CommandConstants.ABBREVIATE_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.COMMAND_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.DESCRIPTION_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.OPTION_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.PARAMETER_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.REQUIRED_OPTION_MARKER;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_OPTIONS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SYNOPSIS_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.USAGE_HELP_AUTO_WIDTH;

import org.apache.ignite.internal.cli.commands.ExitCommand;
import org.apache.ignite.internal.cli.commands.sql.help.SqlHelpCommand;
import picocli.CommandLine.Command;
Expand All @@ -31,7 +42,18 @@
ClearScreen.class,
ExitCommand.class,
SqlHelpCommand.class
}
},

descriptionHeading = DESCRIPTION_HEADING,
optionListHeading = OPTION_LIST_HEADING,
synopsisHeading = SYNOPSIS_HEADING,
requiredOptionMarker = REQUIRED_OPTION_MARKER,
usageHelpAutoWidth = USAGE_HELP_AUTO_WIDTH,
sortOptions = SORT_OPTIONS,
sortSynopsis = SORT_SYNOPSIS,
abbreviateSynopsis = ABBREVIATE_SYNOPSIS,
commandListHeading = COMMAND_LIST_HEADING,
parameterListHeading = PARAMETER_LIST_HEADING
)
public class SqlReplTopLevelCliCommand {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@

package org.apache.ignite.internal.cli.commands.sql.help;

import static org.apache.ignite.internal.cli.commands.CommandConstants.ABBREVIATE_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.COMMAND_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.DESCRIPTION_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.OPTION_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.PARAMETER_LIST_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.REQUIRED_OPTION_MARKER;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_OPTIONS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SORT_SYNOPSIS;
import static org.apache.ignite.internal.cli.commands.CommandConstants.SYNOPSIS_HEADING;
import static org.apache.ignite.internal.cli.commands.CommandConstants.USAGE_HELP_AUTO_WIDTH;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
Expand All @@ -32,11 +43,22 @@
/** Help command in SQL repl mode. */
@Command(name = "help",
header = "Display help information about the specified SQL command.",
synopsisHeading = "%nUsage: ",
helpCommand = true,
description = {
"%nWhen no SQL command is given, the usage help for the main command is displayed.",
"If a SQL command is specified, the help for that command is shown.%n"}
"If a SQL command is specified, the help for that command is shown.%n"
},

descriptionHeading = DESCRIPTION_HEADING,
optionListHeading = OPTION_LIST_HEADING,
synopsisHeading = SYNOPSIS_HEADING,
requiredOptionMarker = REQUIRED_OPTION_MARKER,
usageHelpAutoWidth = USAGE_HELP_AUTO_WIDTH,
sortOptions = SORT_OPTIONS,
sortSynopsis = SORT_SYNOPSIS,
abbreviateSynopsis = ABBREVIATE_SYNOPSIS,
commandListHeading = COMMAND_LIST_HEADING,
parameterListHeading = PARAMETER_LIST_HEADING
)
public final class SqlHelpCommand implements IHelpCommandInitializable2, Runnable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void withoutOptions() {
assertAll(
() -> assertExitCodeIs(2),
this::assertOutputIsEmpty,
() -> assertErrOutputContains("Missing required argument (specify one of these): (<command> | --file=<file>)")
() -> assertErrOutputContains("Missing required argument (specify one of these): (--file=<file> | <command>)")
);
}

Expand Down

0 comments on commit 35fbde0

Please sign in to comment.