You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/06/20 12:51:14 UTC
[sling-org-apache-sling-committer-cli] branch master updated:
SLING-8519 - Improve CLI parsing and general look and feel
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git
The following commit(s) were added to refs/heads/master by this push:
new 57c186e SLING-8519 - Improve CLI parsing and general look and feel
57c186e is described below
commit 57c186ebcd06117e9676c93728afbb901939dfc5
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Thu Jun 20 14:51:09 2019 +0200
SLING-8519 - Improve CLI parsing and general look and feel
* refactored commands to rely on picocli
---
README.md | 21 +--
bnd.bnd | 1 +
pom.xml | 6 +
src/main/features/app.json | 3 +
.../java/org/apache/sling/cli/impl/CLIGroup.java | 24 ++++
.../java/org/apache/sling/cli/impl/Command.java | 14 +-
.../apache/sling/cli/impl/CommandProcessor.java | 157 ++++++++++++---------
.../apache/sling/cli/impl/ExecutionContext.java | 89 ------------
.../org/apache/sling/cli/impl/ExecutionMode.java | 36 +++++
.../cli/impl/release/CreateJiraVersionCommand.java | 47 ++++--
.../apache/sling/cli/impl/release/ListCommand.java | 23 +--
.../cli/impl/release/PrepareVoteEmailCommand.java | 133 +++++++++--------
.../sling/cli/impl/release/ReleaseCLIGroup.java | 39 +++++
.../sling/cli/impl/release/ReusableCLIOptions.java | 34 +++++
.../sling/cli/impl/release/TallyVotesCommand.java | 28 ++--
.../cli/impl/release/UpdateLocalSiteCommand.java | 31 ++--
.../cli/impl/release/UpdateReporterCommand.java | 35 +++--
.../impl/release/PrepareVoteEmailCommandTest.java | 20 ++-
.../cli/impl/release/TallyVotesCommandTest.java | 29 +++-
.../impl/release/UpdateReporterCommandTest.java | 34 +++--
20 files changed, 506 insertions(+), 298 deletions(-)
diff --git a/README.md b/README.md
index 5288c99..6e1da2c 100644
--- a/README.md
+++ b/README.md
@@ -16,21 +16,24 @@ The image is built using `mvn package`. Afterwards it may be run with
docker run --env-file=./docker-env apache/sling-cli
-This invocation produces a list of available subcommands.
+This invocation produces a list of available commands.
## Commands
The commands can be executed in 3 different modes:
- * `dry-run` (default mode) - commands only list their output without performing any actions on the user's behalf
- * `interactive` - commands list their output but ask for user confirmation when it comes to performing an action on the user's behalf
- * `auto` - comands list their output and assume that all questions are provided the default answers when it comes to performing an action on the user's behalf
+ * `DRY_RUN` (default mode) - commands only list their output without performing any actions on the user's behalf
+ * `INTERACTIVE` - commands list their output but ask for user confirmation when it comes to performing an action on the user's behalf
+ * `AUTO` - commands list their output and assume that all questions are provided the default answers when it comes to performing an
+ action on the user's behalf
To select a non-default execution mode provide the mode as an argument to the command:
- docker run -it --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID --interactive
+ docker run -it --env-file=./docker-env apache/sling-cli release prepare-email --repository=$STAGING_REPOSITORY
+ --execution-mode=INTERACTIVE
-Note that for running commands in the `interactive` mode you need to run the Docker container in interactive mode with a pseudo-tty attached (e.g. `docker run -it ...`).
+Note that for running commands in the `INTERACTIVE` mode you need to run the Docker container in interactive mode with a pseudo-tty
+attached (e.g. `docker run -it ...`).
Listing active releases
@@ -38,15 +41,15 @@ Listing active releases
Generating a release vote email
- docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
+ docker run --env-file=./docker-env apache/sling-cli release prepare-email --repository=$STAGING_REPOSITORY_ID
Generating a release vote result email
- docker run --env-file=./docker-env apache/sling-cli release tally-votes $STAGING_REPOSITORY_ID
+ docker run --env-file=./docker-env apache/sling-cli release tally-votes --repository=$STAGING_REPOSITORY_ID
Generating the website update (only diff for now)
- docker run --env-file=docker-env apache/sling-cli release update-local-site $STAGING_REPOSITORY_ID
+ docker run --env-file=docker-env apache/sling-cli release update-local-site --repository=$STAGING_REPOSITORY_ID
## Assumptions
diff --git a/bnd.bnd b/bnd.bnd
index e69de29..78e45e3 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -0,0 +1 @@
+Import-Package: !org.fusesource.jansi, *
diff --git a/pom.xml b/pom.xml
index 09766e2..0e21a19 100644
--- a/pom.xml
+++ b/pom.xml
@@ -224,6 +224,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>info.picocli</groupId>
+ <artifactId>picocli</artifactId>
+ <version>4.0.0-beta-2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
diff --git a/src/main/features/app.json b/src/main/features/app.json
index d8a2bb2..739d561 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -70,6 +70,9 @@
},
{
"id": "org.apache.sling:org.apache.sling.javax.activation:0.1.0"
+ },
+ {
+ "id": "info.picocli:picocli:4.0.0-beta-2"
}
]
}
diff --git a/src/main/java/org/apache/sling/cli/impl/CLIGroup.java b/src/main/java/org/apache/sling/cli/impl/CLIGroup.java
new file mode 100644
index 0000000..ead6ba5
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/CLIGroup.java
@@ -0,0 +1,24 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.cli.impl;
+
+/**
+ * Marker interface for CLI sub-command groups.
+ */
+public interface CLIGroup extends Runnable {}
diff --git a/src/main/java/org/apache/sling/cli/impl/Command.java b/src/main/java/org/apache/sling/cli/impl/Command.java
index 70a6c45..57b9e50 100644
--- a/src/main/java/org/apache/sling/cli/impl/Command.java
+++ b/src/main/java/org/apache/sling/cli/impl/Command.java
@@ -16,14 +16,12 @@
*/
package org.apache.sling.cli.impl;
-
-import org.jetbrains.annotations.NotNull;
-
-public interface Command {
+/**
+ * Marker interface for {@code Commands} supported by the Apache Sling Committer CLI.
+ */
+public interface Command extends Runnable {
- String PROPERTY_NAME_COMMAND = "command";
- String PROPERTY_NAME_SUBCOMMAND = "subcommand";
- String PROPERTY_NAME_SUMMARY = "summary";
+ String PROPERTY_NAME_COMMAND_GROUP = "command.group";
+ String PROPERTY_NAME_COMMAND_NAME = "command.name";
- void execute(@NotNull ExecutionContext context) throws Exception;
}
diff --git a/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
index 9029d4b..20f083a 100644
--- a/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
+++ b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
@@ -16,10 +16,15 @@
*/
package org.apache.sling.cli.impl;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.sling.cli.impl.release.ReleaseCLIGroup;
+import org.jetbrains.annotations.NotNull;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
@@ -30,9 +35,15 @@ import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE;
import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC;
+@CommandLine.Command(
+ name = "docker run -it --env-file=./docker-env apache/sling-cli",
+ description = "Apache Sling Committers CLI"
+)
@Component(service = CommandProcessor.class)
public class CommandProcessor {
@@ -40,7 +51,14 @@ public class CommandProcessor {
private static final String EXEC_ARGS = "exec.args";
private BundleContext ctx;
- private Map<CommandKey, CommandWithProps> commands = new ConcurrentHashMap<>();
+ private static final Map<String, Class> CLI_GROUPS;
+
+ static {
+ CLI_GROUPS = new HashMap<>();
+ CLI_GROUPS.put("release", ReleaseCLIGroup.class);
+ }
+
+ private Map<String, TreeSet<CommandWithProps>> commands = new ConcurrentHashMap<>();
@Activate
private void activate(BundleContext ctx) {
@@ -49,26 +67,56 @@ public class CommandProcessor {
@Reference(service = Command.class, cardinality = MULTIPLE, policy = DYNAMIC)
protected void bindCommand(Command cmd, Map<String, ?> props) {
- commands.put(CommandKey.of(props), CommandWithProps.of(cmd, props));
+ CommandWithProps commandWithProps = CommandWithProps.of(cmd, props);
+ Set<CommandWithProps> bucket = commands.computeIfAbsent(commandWithProps.group, key -> new TreeSet<>());
+ bucket.add(commandWithProps);
}
- protected void unbindCommand(Map<String, ?> props) {
- commands.remove(CommandKey.of(props));
+ protected void unbindCommand(Command cmd, Map<String, ?> props) {
+ CommandWithProps commandWithProps = CommandWithProps.of(cmd, props);
+ Set<CommandWithProps> bucket = commands.get(commandWithProps.group);
+ if (bucket != null) {
+ bucket.remove(commandWithProps);
+ if (bucket.isEmpty()) {
+ commands.remove(commandWithProps.group);
+ }
+ }
+
}
void runCommand() {
- String[] arguments = arguments(ctx.getProperty(EXEC_ARGS));
- CommandKey key = CommandKey.of(arguments);
- ExecutionContext context = defineContext(arguments);
+ System.setProperty("picocli.usage.width", "140");
+ CommandLine commandLine = new CommandLine(this);
+ commandLine.addSubcommand(CommandLine.HelpCommand.class);
+ for (Map.Entry<String, TreeSet<CommandWithProps>> entry : commands.entrySet()) {
+ String group = entry.getKey();
+ Class<?> groupClass = CLI_GROUPS.get(group);
+ if (groupClass != null) {
+ CommandLine secondary = new CommandLine(groupClass);
+ for (CommandWithProps command : entry.getValue()) {
+ secondary.addSubcommand(command.name, command.cmd);
+ }
+ secondary.addSubcommand(CommandLine.HelpCommand.class);
+ commandLine.addSubcommand(group, secondary);
+ } else {
+ for (CommandWithProps command : entry.getValue()) {
+ commandLine.addSubcommand(command.group, command.cmd);
+ }
+ }
+ }
+ int commandExitCode;
try {
- commands.getOrDefault(key, new CommandWithProps(ignored -> {
- logger.info("Usage: sling command sub-command [target]");
- logger.info("");
- logger.info("Available commands:");
- commands.forEach((k, c) -> logger.info("{} {}: {}", k.command, k.subCommand, c.summary));
- }, "")).cmd.execute(context);
+ String[] arguments = arguments(ctx.getProperty(EXEC_ARGS));
+ commandExitCode = commandLine.execute(arguments);
+ } catch (CommandLine.ParameterException e) {
+ commandLine.getErr().println(e.getMessage());
+ if (!CommandLine.UnmatchedArgumentException.printSuggestions(e, commandLine.getErr())) {
+ e.getCommandLine().usage(commandLine.getErr());
+ }
+ commandExitCode = commandLine.getCommandSpec().exitCodeOnInvalidInput();
} catch (Exception e) {
- logger.warn("Failed running command", e);
+ logger.warn("Failed running command.", e);
+ commandExitCode = 1;
} finally {
try {
ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(Framework.class).stop();
@@ -77,6 +125,7 @@ public class CommandProcessor {
System.exit(1);
}
}
+ System.exit(commandExitCode);
}
private String[] arguments(String cliSpec) {
@@ -86,70 +135,52 @@ public class CommandProcessor {
return cliSpec.split(" ");
}
- private ExecutionContext defineContext(String[] arguments) {
- if (arguments.length < 3)
- return ExecutionContext.DEFAULT;
- String target = arguments[2];
- if (arguments.length > 3) {
- return new ExecutionContext(ExecutionContext.Mode.fromString(arguments[3]), target);
- } else {
- return new ExecutionContext(ExecutionContext.Mode.DRY_RUN, target);
- }
- }
-
-
- static class CommandKey {
-
- private static final CommandKey EMPTY = new CommandKey("", "");
-
- private final String command;
- private final String subCommand;
-
- static CommandKey of(String[] arguments) {
- if (arguments.length < 2)
- return EMPTY;
+ static class CommandWithProps implements Comparable<CommandWithProps> {
+ private final String group;
+ private final String name;
+ private final Command cmd;
- return new CommandKey(arguments[0], arguments[1]);
+ static CommandWithProps of(Command cmd, Map<String, ?> props) {
+ return new CommandWithProps(
+ cmd,
+ (String) props.get(Command.PROPERTY_NAME_COMMAND_GROUP),
+ (String) props.get(Command.PROPERTY_NAME_COMMAND_NAME)
+ );
}
- static CommandKey of(Map<String, ?> serviceProps) {
- return new CommandKey((String) serviceProps.get(Command.PROPERTY_NAME_COMMAND), (String) serviceProps.get(Command.PROPERTY_NAME_SUBCOMMAND));
+ CommandWithProps(Command cmd, String group, String name) {
+ this.cmd = cmd;
+ this.group = group;
+ this.name = name;
}
- CommandKey(String command, String subCommand) {
- this.command = command;
- this.subCommand = subCommand;
+ @Override
+ public int compareTo(@NotNull CommandProcessor.CommandWithProps o) {
+ if (!group.equals(o.group)) {
+ return group.compareTo(o.group);
+ } else {
+ return name.compareTo(o.name);
+ }
}
@Override
public int hashCode() {
- return Objects.hash(command, subCommand);
+ return Objects.hash(group, name);
}
@Override
public boolean equals(Object obj) {
- if (this == obj)
+ if (this == obj) {
return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
+ }
+ if (obj == null) {
return false;
- CommandKey other = (CommandKey) obj;
- return Objects.equals(command, other.command) && Objects.equals(subCommand, other.subCommand);
- }
- }
-
- static class CommandWithProps {
- private final Command cmd;
- private final String summary;
-
- static CommandWithProps of(Command cmd, Map<String, ?> props) {
- return new CommandWithProps(cmd, (String) props.get(Command.PROPERTY_NAME_SUMMARY));
- }
-
- CommandWithProps(Command cmd, String summary) {
- this.cmd = cmd;
- this.summary = summary;
+ }
+ if (obj instanceof CommandWithProps) {
+ CommandWithProps other = (CommandWithProps) obj;
+ return Objects.equals(group, other.group) && Objects.equals(name, other.name);
+ }
+ return false;
}
}
}
diff --git a/src/main/java/org/apache/sling/cli/impl/ExecutionContext.java b/src/main/java/org/apache/sling/cli/impl/ExecutionContext.java
deleted file mode 100644
index 6505508..0000000
--- a/src/main/java/org/apache/sling/cli/impl/ExecutionContext.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ~ 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.sling.cli.impl;
-
-import java.util.Objects;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Defines the way a {@link Command} will be executed, together with the {@code command}'s execution target.
- */
-public class ExecutionContext {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionContext.class);
-
- private final Mode mode;
- private final String target;
- public static final ExecutionContext DEFAULT = new ExecutionContext(Mode.DRY_RUN, null);
-
- /**
- * Creates an {@code ExecutionContext}.
- *
- * @param target the command's target
- * @param mode the execution mode
- */
- public ExecutionContext(@NotNull Mode mode, @Nullable String target) {
- this.mode = mode;
- this.target = Objects.requireNonNullElse(target, "");
- }
-
- /**
- * Returns the execution target for a command.
- *
- * @return the execution target
- */
- @NotNull
- public String getTarget() {
- return target;
- }
-
- /**
- * Returns the execution mode for a command.
- *
- * @return the execution mode
- */
- @NotNull
- public Mode getMode() {
- return mode;
- }
-
- public enum Mode {
- DRY_RUN("--dry-run"), INTERACTIVE("--interactive"), AUTO("--auto");
-
- private final String string;
-
- Mode(String string) {
- this.string = string;
- }
-
- static Mode fromString(String value) {
- for (Mode m : values()) {
- if (m.string.equals(value)) {
- return m;
- }
- }
- LOGGER.warn("Unknown command execution mode {}. Switching to default mode {}.", value, DRY_RUN.string);
- return DRY_RUN;
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/cli/impl/ExecutionMode.java b/src/main/java/org/apache/sling/cli/impl/ExecutionMode.java
new file mode 100644
index 0000000..bf879af
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/ExecutionMode.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.cli.impl;
+
+public enum ExecutionMode {
+ /**
+ * In {@code DRY_RUN} mode {@link Command}s should only output what actions they would execute.
+ */
+ DRY_RUN,
+
+ /**
+ * In {@code INTERACTIVE} mode {@link Command}s should always ask for user confirmation for each action they would execute.
+ */
+ INTERACTIVE,
+
+ /**
+ * In {@code AUTO} mode {@link Command}s should execute their normal actions assuming the user has provided all the default answers.
+ */
+ AUTO;
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java b/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
index 6605268..c0bd5d0 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
@@ -20,7 +20,7 @@ import java.io.IOException;
import java.util.List;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
+import org.apache.sling.cli.impl.ExecutionMode;
import org.apache.sling.cli.impl.InputOption;
import org.apache.sling.cli.impl.UserInput;
import org.apache.sling.cli.impl.jira.Issue;
@@ -28,31 +28,48 @@ import org.apache.sling.cli.impl.jira.Version;
import org.apache.sling.cli.impl.jira.VersionClient;
import org.apache.sling.cli.impl.nexus.StagingRepository;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Component(service = Command.class, property = {
- Command.PROPERTY_NAME_COMMAND+"=release",
- Command.PROPERTY_NAME_SUBCOMMAND+"=create-jira-new-version",
- Command.PROPERTY_NAME_SUMMARY+"=Creates a new Jira version, if needed, and transitions any unresolved issues from the version being released to the next one."
- })
+import picocli.CommandLine;
+
+@Component(service = Command.class,
+ property = {
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + CreateJiraVersionCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + CreateJiraVersionCommand.NAME
+ }
+)
+@CommandLine.Command(
+ name = CreateJiraVersionCommand.NAME,
+ description = "Creates a new Jira version, if needed, and transitions any unresolved issues from the version being released to " +
+ "the next one",
+ subcommands = CommandLine.HelpCommand.class
+)
public class CreateJiraVersionCommand implements Command {
+ static final String GROUP = "release";
+ static final String NAME = "create-new-jira-version";
+
+ @CommandLine.Option(names = {"-r", "--repository"}, description = "Nexus repository id", required = true)
+ private Integer repositoryId;
+
@Reference
private StagingRepositoryFinder repoFinder;
@Reference
private VersionClient versionClient;
+ @CommandLine.Mixin
+ private ReusableCLIOptions reusableCLIOptions;
+
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
- StagingRepository repo = repoFinder.find(Integer.parseInt(context.getTarget()));
+ StagingRepository repo = repoFinder.find(repositoryId);
for (Release release : Release.fromString(repo.getDescription()) ) {
Version version = versionClient.find(release);
logger.info("Found {}.", version);
@@ -60,13 +77,13 @@ public class CreateJiraVersionCommand implements Command {
boolean createNextRelease = false;
if ( successorVersion == null ) {
Release next = release.next();
- if (context.getMode() == ExecutionContext.Mode.DRY_RUN) {
+ if (reusableCLIOptions.executionMode == ExecutionMode.DRY_RUN) {
logger.info("Version {} would be created.", next.getName());
- } else if (context.getMode() == ExecutionContext.Mode.INTERACTIVE) {
+ } else if (reusableCLIOptions.executionMode == ExecutionMode.INTERACTIVE) {
InputOption answer = UserInput.yesNo(String.format("Should version %s be created?", next.getName()),
InputOption.YES);
createNextRelease = (answer == InputOption.YES);
- } else if (context.getMode() == ExecutionContext.Mode.AUTO) {
+ } else if (reusableCLIOptions.executionMode == ExecutionMode.AUTO) {
createNextRelease = true;
}
if (createNextRelease) {
@@ -81,16 +98,16 @@ public class CreateJiraVersionCommand implements Command {
List<Issue> unresolvedIssues = versionClient.findUnresolvedIssues(release);
if (!unresolvedIssues.isEmpty()) {
boolean moveIssues = false;
- if (context.getMode() == ExecutionContext.Mode.DRY_RUN) {
+ if (reusableCLIOptions.executionMode == ExecutionMode.DRY_RUN) {
logger.info("{} unresolved issues would be moved from version {} to version {} :",
unresolvedIssues.size(), version.getName(), successorVersion.getName());
- } else if (context.getMode() == ExecutionContext.Mode.INTERACTIVE) {
+ } else if (reusableCLIOptions.executionMode == ExecutionMode.INTERACTIVE) {
InputOption answer = UserInput.yesNo(String.format("Should the %s unresolved issue(s) from version %s be " +
"moved " +
"to version %s?", unresolvedIssues.size(), version.getName(), successorVersion.getName()),
InputOption.YES);
moveIssues = (answer == InputOption.YES);
- } else if (context.getMode() == ExecutionContext.Mode.AUTO) {
+ } else if (reusableCLIOptions.executionMode == ExecutionMode.AUTO) {
moveIssues = true;
}
if (moveIssues) {
diff --git a/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java b/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java
index 4a59b7f..e4115ef 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java
@@ -19,30 +19,35 @@ package org.apache.sling.cli.impl.release;
import java.io.IOException;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Component(service = Command.class, property = {
- Command.PROPERTY_NAME_COMMAND + "=release",
- Command.PROPERTY_NAME_SUBCOMMAND + "=list",
- Command.PROPERTY_NAME_SUMMARY + "=Lists all open releases" })
+import picocli.CommandLine;
+
+@Component(service = Command.class,
+ property = {
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + ListCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + ListCommand.NAME,
+ }
+)
+@CommandLine.Command(name = ListCommand.NAME, description = "Lists all open releases", subcommands = CommandLine.HelpCommand.class)
public class ListCommand implements Command {
+ static final String GROUP = "release";
+ static final String NAME = "list";
+
private final Logger logger = LoggerFactory.getLogger(getClass());
@Reference
private StagingRepositoryFinder repoFinder;
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
- repoFinder.list().stream()
- .forEach( r -> logger.info("{}\t{}", r.getRepositoryId(), r.getDescription()));
+ repoFinder.list().forEach( r -> logger.info("{}\t{}", r.getRepositoryId(), r.getDescription()));
} catch (IOException e) {
logger.warn("Failed executing command", e);
}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index aff45f0..99dbb96 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -23,7 +23,6 @@ import java.util.stream.Collectors;
import javax.mail.internet.InternetAddress;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
import org.apache.sling.cli.impl.InputOption;
import org.apache.sling.cli.impl.UserInput;
import org.apache.sling.cli.impl.jira.Version;
@@ -33,18 +32,29 @@ import org.apache.sling.cli.impl.nexus.StagingRepository;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
import org.apache.sling.cli.impl.people.Member;
import org.apache.sling.cli.impl.people.MembersFinder;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Component(service = Command.class, property = {
- Command.PROPERTY_NAME_COMMAND + "=release",
- Command.PROPERTY_NAME_SUBCOMMAND + "=prepare-email",
- Command.PROPERTY_NAME_SUMMARY + "=Prepares an email vote for the specified release." })
+import picocli.CommandLine;
+
+@Component(service = Command.class,
+ property = {
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + PrepareVoteEmailCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + PrepareVoteEmailCommand.NAME
+ }
+)
+@CommandLine.Command(
+ name = PrepareVoteEmailCommand.NAME,
+ description = "Prepares an email vote for the releases found in the Nexus staged repository",
+ subcommands = CommandLine.HelpCommand.class
+)
public class PrepareVoteEmailCommand implements Command {
+ static final String GROUP = "release";
+ static final String NAME = "prepare-email";
+
private static final Logger LOGGER = LoggerFactory.getLogger(PrepareVoteEmailCommand.class);
@Reference
@@ -59,6 +69,15 @@ public class PrepareVoteEmailCommand implements Command {
@Reference
private Mailer mailer;
+ @CommandLine.Option(names = {"-r", "--repository"}, description = "Nexus repository id", required = true)
+ private Integer repositoryId;
+
+ @CommandLine.Mixin
+ private ReusableCLIOptions reusableCLIOptions;
+
+ @CommandLine.Spec
+ CommandLine.Model.CommandSpec spec;
+
// TODO - replace with file template
private static final String EMAIL_TEMPLATE =
"From: ##FROM##\n" +
@@ -95,59 +114,63 @@ public class PrepareVoteEmailCommand implements Command {
"https://issues.apache.org/jira/browse/SLING/fixforversion/##VERSION_ID##";
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
- int repoId = Integer.parseInt(context.getTarget());
- StagingRepository repo = repoFinder.find(repoId);
- List<Release> releases = Release.fromString(repo.getDescription());
- List<Version> versions = releases.stream()
- .map( r -> versionClient.find(r))
- .collect(Collectors.toList());
-
- String releaseName = releases.stream()
- .map( Release::getFullName )
- .collect(Collectors.joining(", "));
-
- int fixedIssueCounts = versions.stream().mapToInt( Version::getIssuesFixedCount).sum();
- String releaseOrReleases = versions.size() > 1 ?
- "these releases" : "this release";
-
- String releaseJiraLinks = versions.stream()
- .map( v -> RELEASE_TEMPLATE.replace("##VERSION_ID##", String.valueOf(v.getId())))
- .collect(Collectors.joining("\n"));
-
- Member currentMember = membersFinder.getCurrentMember();
- String emailContents = EMAIL_TEMPLATE
- .replace("##FROM##", new InternetAddress(currentMember.getEmail(), currentMember.getName()).toString())
- .replace("##RELEASE_NAME##", releaseName)
- .replace("##RELEASE_ID##", String.valueOf(repoId))
- .replace("##RELEASE_OR_RELEASES##", releaseOrReleases)
- .replace("##RELEASE_JIRA_LINKS##", releaseJiraLinks)
- .replace("##FIXED_ISSUES_COUNT##", String.valueOf(fixedIssueCounts))
- .replace("##USER_NAME##", currentMember.getName());
- switch (context.getMode()) {
- case DRY_RUN:
- LOGGER.info("The following email would be sent from your @apache.org address (see the \"From:\" header):\n");
- LOGGER.info(emailContents);
- break;
- case INTERACTIVE:
- String question ="Should the following email be sent from your @apache.org address (see the" +
- " \"From:\" header)?\n\n" + emailContents;
- InputOption answer = UserInput.yesNo(question, InputOption.YES);
- if (InputOption.YES.equals(answer)) {
+ CommandLine commandLine = spec.commandLine();
+ if (commandLine.isUsageHelpRequested()) {
+ commandLine.usage(commandLine.getOut());
+ } else {
+ StagingRepository repo = repoFinder.find(repositoryId);
+ List<Release> releases = Release.fromString(repo.getDescription());
+ List<Version> versions = releases.stream()
+ .map(r -> versionClient.find(r))
+ .collect(Collectors.toList());
+
+ String releaseName = releases.stream()
+ .map(Release::getFullName)
+ .collect(Collectors.joining(", "));
+
+ int fixedIssueCounts = versions.stream().mapToInt(Version::getIssuesFixedCount).sum();
+ String releaseOrReleases = versions.size() > 1 ?
+ "these releases" : "this release";
+
+ String releaseJiraLinks = versions.stream()
+ .map(v -> RELEASE_TEMPLATE.replace("##VERSION_ID##", String.valueOf(v.getId())))
+ .collect(Collectors.joining("\n"));
+
+ Member currentMember = membersFinder.getCurrentMember();
+ String emailContents = EMAIL_TEMPLATE
+ .replace("##FROM##", new InternetAddress(currentMember.getEmail(), currentMember.getName()).toString())
+ .replace("##RELEASE_NAME##", releaseName)
+ .replace("##RELEASE_ID##", String.valueOf(repositoryId))
+ .replace("##RELEASE_OR_RELEASES##", releaseOrReleases)
+ .replace("##RELEASE_JIRA_LINKS##", releaseJiraLinks)
+ .replace("##FIXED_ISSUES_COUNT##", String.valueOf(fixedIssueCounts))
+ .replace("##USER_NAME##", currentMember.getName());
+ switch (reusableCLIOptions.executionMode) {
+ case DRY_RUN:
+ LOGGER.info("The following email would be sent from your @apache.org address (see the \"From:\" header):\n");
+ LOGGER.info(emailContents);
+ break;
+ case INTERACTIVE:
+ String question = "Should the following email be sent from your @apache.org address (see the" +
+ " \"From:\" header)?\n\n" + emailContents;
+ InputOption answer = UserInput.yesNo(question, InputOption.YES);
+ if (InputOption.YES.equals(answer)) {
+ LOGGER.info("Sending email...");
+ mailer.send(emailContents);
+ LOGGER.info("Done!");
+ } else if (InputOption.NO.equals(answer)) {
+ LOGGER.info("Aborted.");
+ }
+ break;
+ case AUTO:
+ LOGGER.info(emailContents);
LOGGER.info("Sending email...");
mailer.send(emailContents);
LOGGER.info("Done!");
- } else if (InputOption.NO.equals(answer)) {
- LOGGER.info("Aborted.");
- }
- break;
- case AUTO:
- LOGGER.info(emailContents);
- LOGGER.info("Sending email...");
- mailer.send(emailContents);
- LOGGER.info("Done!");
- break;
+ break;
+ }
}
} catch (IOException e) {
LOGGER.warn("Failed executing command", e);
diff --git a/src/main/java/org/apache/sling/cli/impl/release/ReleaseCLIGroup.java b/src/main/java/org/apache/sling/cli/impl/release/ReleaseCLIGroup.java
new file mode 100644
index 0000000..1700765
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/ReleaseCLIGroup.java
@@ -0,0 +1,39 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.cli.impl.release;
+
+import org.apache.sling.cli.impl.CLIGroup;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+ description = "Performs release-related operations"
+)
+public class ReleaseCLIGroup implements CLIGroup {
+
+ private ReleaseCLIGroup(){}
+
+ @CommandLine.Spec
+ private CommandLine.Model.CommandSpec commandSpec;
+
+ @Override
+ public void run() {
+ commandSpec.commandLine().usage(System.console().writer());
+ }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/ReusableCLIOptions.java b/src/main/java/org/apache/sling/cli/impl/release/ReusableCLIOptions.java
new file mode 100644
index 0000000..afa4ef4
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/ReusableCLIOptions.java
@@ -0,0 +1,34 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.cli.impl.release;
+
+import org.apache.sling.cli.impl.ExecutionMode;
+
+import picocli.CommandLine;
+
+class ReusableCLIOptions {
+
+ @CommandLine.Option(
+ names = {"-x", "--execution-mode"},
+ defaultValue = "DRY_RUN",
+ description = "execution mode: ${COMPLETION-CANDIDATES}; default: ${DEFAULT-VALUE}")
+ ExecutionMode executionMode;
+
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 8cf26f2..8235819 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -28,7 +28,6 @@ import java.util.stream.Collectors;
import javax.mail.internet.InternetAddress;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
import org.apache.sling.cli.impl.InputOption;
import org.apache.sling.cli.impl.UserInput;
import org.apache.sling.cli.impl.mail.Email;
@@ -38,19 +37,25 @@ import org.apache.sling.cli.impl.nexus.StagingRepository;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
import org.apache.sling.cli.impl.people.Member;
import org.apache.sling.cli.impl.people.MembersFinder;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
@Component(service = Command.class, property = {
- Command.PROPERTY_NAME_COMMAND+"=release",
- Command.PROPERTY_NAME_SUBCOMMAND+"=tally-votes",
- Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + TallyVotesCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + TallyVotesCommand.NAME
})
+@CommandLine.Command(name = TallyVotesCommand.NAME,
+ description = "Counts votes cast for a release and generates the result email",
+ subcommands = CommandLine.HelpCommand.class)
public class TallyVotesCommand implements Command {
+ static final String GROUP = "release";
+ static final String NAME = "tally-votes";
+
private static final Logger LOGGER = LoggerFactory.getLogger(TallyVotesCommand.class);
@Reference
@@ -65,6 +70,12 @@ public class TallyVotesCommand implements Command {
@Reference
private Mailer mailer;
+ @CommandLine.Option(names = {"-r", "--repository"}, description = "Nexus repository id", required = true)
+ private Integer repositoryId;
+
+ @CommandLine.Mixin
+ private ReusableCLIOptions reusableCLIOptions;
+
// TODO - move to file
private static final String EMAIL_TEMPLATE =
"From: ##FROM## \n" +
@@ -86,10 +97,9 @@ public class TallyVotesCommand implements Command {
"\n";
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
-
- StagingRepository repository = repoFinder.find(Integer.parseInt(context.getTarget()));
+ StagingRepository repository = repoFinder.find(repositoryId);
List<Release> releases = Release.fromString(repository.getDescription());
String releaseName = releases.stream().map(Release::getName).collect(Collectors.joining(", "));
String releaseFullName = releases.stream().map(Release::getFullName).collect(Collectors.joining(", "));
@@ -130,7 +140,7 @@ public class TallyVotesCommand implements Command {
}
if (bindingVoters.size() >= 3) {
- switch (context.getMode()) {
+ switch (reusableCLIOptions.executionMode) {
case DRY_RUN:
LOGGER.info("The following email would be sent from your @apache.org address (see the \"From:\" header):\n");
LOGGER.info(email);
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
index d9ad68b..14cad7e 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -24,7 +24,6 @@ import java.time.LocalDateTime;
import java.util.List;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
import org.apache.sling.cli.impl.jbake.JBakeContentUpdater;
import org.apache.sling.cli.impl.nexus.StagingRepository;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
@@ -32,33 +31,43 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Component(service = Command.class, property = {
- Command.PROPERTY_NAME_COMMAND+"=release",
- Command.PROPERTY_NAME_SUBCOMMAND+"=update-local-site",
- Command.PROPERTY_NAME_SUMMARY+"=Updates the Sling website with the new release information, based on a local checkout"
-})
+import picocli.CommandLine;
+
+@Component(service = Command.class,
+ property = {
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + UpdateLocalSiteCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + UpdateLocalSiteCommand.NAME
+ }
+)
+@CommandLine.Command(name = UpdateLocalSiteCommand.NAME, description = "Updates the Sling website with the new release information, " +
+ "based on a local checkout", subcommands = CommandLine.HelpCommand.class)
public class UpdateLocalSiteCommand implements Command {
-
+
+ static final String GROUP = "release";
+ static final String NAME = "update-local-site";
+
private static final String GIT_CHECKOUT = "/tmp/sling-site";
@Reference
private StagingRepositoryFinder repoFinder;
private final Logger logger = LoggerFactory.getLogger(getClass());
-
+
+ @CommandLine.Option(names = {"-r", "--repository"}, description = "Nexus repository id", required = true)
+ private Integer repositoryId;
+
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
ensureRepo();
try ( Git git = Git.open(new File(GIT_CHECKOUT)) ) {
- StagingRepository repository = repoFinder.find(Integer.parseInt(context.getTarget()));
+ StagingRepository repository = repoFinder.find(repositoryId);
List<Release> releases = Release.fromString(repository.getDescription());
JBakeContentUpdater updater = new JBakeContentUpdater();
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
index d613487..a78ff22 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -32,27 +32,34 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
import org.apache.sling.cli.impl.InputOption;
import org.apache.sling.cli.impl.UserInput;
import org.apache.sling.cli.impl.http.HttpClientFactory;
import org.apache.sling.cli.impl.nexus.StagingRepository;
import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
-import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
@Component(service = Command.class,
- property = {
- Command.PROPERTY_NAME_COMMAND + "=release",
- Command.PROPERTY_NAME_SUBCOMMAND + "=update-reporter",
- Command.PROPERTY_NAME_SUMMARY + "=Updates the Apache Reporter System with the new release information"
- }
+ property = {
+ Command.PROPERTY_NAME_COMMAND_GROUP + "=" + UpdateReporterCommand.GROUP,
+ Command.PROPERTY_NAME_COMMAND_NAME + "=" + UpdateReporterCommand.NAME,
+ }
+)
+@CommandLine.Command(
+ name = UpdateReporterCommand.NAME,
+ description = "Updates the Apache Reporter System with the new release information",
+ subcommands = CommandLine.HelpCommand.class
)
public class UpdateReporterCommand implements Command {
+ static final String GROUP = "release";
+ static final String NAME = "update-reporter";
+
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateReporterCommand.class);
@Reference
@@ -61,13 +68,19 @@ public class UpdateReporterCommand implements Command {
@Reference
private HttpClientFactory httpClientFactory;
+ @CommandLine.Option(names = {"-r", "--repository"}, description = "Nexus repository id", required = true)
+ private Integer repositoryId;
+
+ @CommandLine.Mixin
+ private ReusableCLIOptions reusableCLIOptions;
+
@Override
- public void execute(@NotNull ExecutionContext context) {
+ public void run() {
try {
- StagingRepository repository = repoFinder.find(Integer.parseInt(context.getTarget()));
+ StagingRepository repository = repoFinder.find(repositoryId);
List<Release> releases = Release.fromString(repository.getDescription());
String releaseReleases = releases.size() > 1 ? "releases" : "release";
- switch (context.getMode()) {
+ switch (reusableCLIOptions.executionMode) {
case DRY_RUN:
LOGGER.info("The following {} would be added to the Apache Reporter System:", releaseReleases);
releases.forEach(release -> LOGGER.info(" - {}", release.getFullName()));
@@ -93,7 +106,7 @@ public class UpdateReporterCommand implements Command {
}
} catch (IOException e) {
- LOGGER.error(String.format("Unable to update reporter service; passed command: %s.", context.getTarget()), e);
+ LOGGER.error(String.format("Unable to update reporter service; passed command: %s.", repositoryId), e);
}
}
diff --git a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
index d459306..e408509 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
@@ -21,7 +21,7 @@ package org.apache.sling.cli.impl.release;
import java.io.IOException;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
+import org.apache.sling.cli.impl.ExecutionMode;
import org.apache.sling.cli.impl.jira.Version;
import org.apache.sling.cli.impl.jira.VersionClient;
import org.apache.sling.cli.impl.mail.Mailer;
@@ -33,8 +33,12 @@ import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.junit.Rule;
import org.junit.Test;
import org.osgi.framework.ServiceReference;
+import org.powermock.reflect.Whitebox;
+
+import picocli.CommandLine;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,12 +51,22 @@ public class PrepareVoteEmailCommandTest {
public void testPrepareEmailGeneration() throws Exception {
Mailer mailer = mock(Mailer.class);
prepareExecution(mailer);
- osgiContext.registerInjectActivateService(new PrepareVoteEmailCommand());
+ PrepareVoteEmailCommand prepareVoteEmailCommand = spy(new PrepareVoteEmailCommand());
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ CommandLine.Model.CommandSpec commandSpec = mock(CommandLine.Model.CommandSpec.class);
+ CommandLine commandLine = mock(CommandLine.class);
+ when(commandSpec.commandLine()).thenReturn(commandLine);
+ when(commandLine.isUsageHelpRequested()).thenReturn(false);
+ Whitebox.setInternalState(prepareVoteEmailCommand, "spec", commandSpec);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.AUTO);
+ Whitebox.setInternalState(prepareVoteEmailCommand, "reusableCLIOptions", reusableCLIOptions);
+ Whitebox.setInternalState(prepareVoteEmailCommand, "repositoryId", 123);
+ osgiContext.registerInjectActivateService(prepareVoteEmailCommand);
ServiceReference<?> reference =
osgiContext.bundleContext().getServiceReference(Command.class.getName());
Command command = (Command) osgiContext.bundleContext().getService(reference);
- command.execute(new ExecutionContext(ExecutionContext.Mode.AUTO, "123"));
+ command.run();
verify(mailer).send(
"From: John Doe <jo...@apache.org>\n" +
"To: \"Sling Developers List\" <de...@sling.apache.org>\n" +
diff --git a/src/test/java/org/apache/sling/cli/impl/release/TallyVotesCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/TallyVotesCommandTest.java
index 6b8bece..c61cc7f 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/TallyVotesCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/TallyVotesCommandTest.java
@@ -29,7 +29,7 @@ import javax.mail.internet.InternetAddress;
import org.apache.sling.cli.impl.Command;
import org.apache.sling.cli.impl.Credentials;
import org.apache.sling.cli.impl.CredentialsService;
-import org.apache.sling.cli.impl.ExecutionContext;
+import org.apache.sling.cli.impl.ExecutionMode;
import org.apache.sling.cli.impl.mail.Email;
import org.apache.sling.cli.impl.mail.Mailer;
import org.apache.sling.cli.impl.mail.VoteThreadFinder;
@@ -81,11 +81,16 @@ public class TallyVotesCommandTest {
add(mockEmail("johndoe@apache.org", "John Doe"));
}};
prepareExecution(mock(Mailer.class), thread);
- osgiContext.registerInjectActivateService(new TallyVotesCommand());
+ TallyVotesCommand tallyVotesCommand = spy(new TallyVotesCommand());
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.DRY_RUN);
+ Whitebox.setInternalState(tallyVotesCommand, "repositoryId", 123);
+ Whitebox.setInternalState(tallyVotesCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(tallyVotesCommand);
ServiceReference<?> reference =
osgiContext.bundleContext().getServiceReference(Command.class.getName());
Command command = (Command) osgiContext.bundleContext().getService(reference);
- command.execute(new ExecutionContext(ExecutionContext.Mode.DRY_RUN, "123"));
+ command.run();
verify(logger).info(
"From: John Doe <jo...@apache.org> \n" +
"To: \"Sling Developers List\" <de...@sling.apache.org>\n" +
@@ -120,11 +125,16 @@ public class TallyVotesCommandTest {
add(mockEmail("daniel@apache.org", "Daniel"));
}};
prepareExecution(mock(Mailer.class), thread);
- osgiContext.registerInjectActivateService(new TallyVotesCommand());
+ TallyVotesCommand tallyVotesCommand = spy(new TallyVotesCommand());
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.DRY_RUN);
+ Whitebox.setInternalState(tallyVotesCommand, "repositoryId", 123);
+ Whitebox.setInternalState(tallyVotesCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(tallyVotesCommand);
ServiceReference<?> reference =
osgiContext.bundleContext().getServiceReference(Command.class.getName());
Command command = (Command) osgiContext.bundleContext().getService(reference);
- command.execute(new ExecutionContext(ExecutionContext.Mode.DRY_RUN, "123"));
+ command.run();
verify(logger).info(
"Release {} does not have at least 3 binding votes.",
"Apache Sling CLI Test 1.0.0"
@@ -144,11 +154,16 @@ public class TallyVotesCommandTest {
}};
Mailer mailer = mock(Mailer.class);
prepareExecution(mailer, thread);
- osgiContext.registerInjectActivateService(new TallyVotesCommand());
+ TallyVotesCommand tallyVotesCommand = spy(new TallyVotesCommand());
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.AUTO);
+ Whitebox.setInternalState(tallyVotesCommand, "repositoryId", 123);
+ Whitebox.setInternalState(tallyVotesCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(tallyVotesCommand);
ServiceReference<?> reference =
osgiContext.bundleContext().getServiceReference(Command.class.getName());
Command command = (Command) osgiContext.bundleContext().getService(reference);
- command.execute(new ExecutionContext(ExecutionContext.Mode.AUTO, "123"));
+ command.run();
verify(mailer).send(
"From: John Doe <jo...@apache.org> \n" +
"To: \"Sling Developers List\" <de...@sling.apache.org>\n" +
diff --git a/src/test/java/org/apache/sling/cli/impl/release/UpdateReporterCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/UpdateReporterCommandTest.java
index ac5f758..ece4d18 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/UpdateReporterCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/UpdateReporterCommandTest.java
@@ -24,7 +24,7 @@ import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.ExecutionContext;
+import org.apache.sling.cli.impl.ExecutionMode;
import org.apache.sling.cli.impl.InputOption;
import org.apache.sling.cli.impl.UserInput;
import org.apache.sling.cli.impl.http.HttpClientFactory;
@@ -38,12 +38,14 @@ import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -81,16 +83,20 @@ public class UpdateReporterCommandTest {
@Test
@PrepareForTest({LoggerFactory.class})
- public void testDryRun() throws Exception {
+ public void testDryRun() {
mockStatic(LoggerFactory.class);
Logger logger = mock(Logger.class);
when(LoggerFactory.getLogger(UpdateReporterCommand.class)).thenReturn(logger);
-
- osgiContext.registerInjectActivateService(new UpdateReporterCommand());
+ UpdateReporterCommand updateReporterCommand = spy(new UpdateReporterCommand());
+ Whitebox.setInternalState(updateReporterCommand, "repositoryId", 42);
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.DRY_RUN);
+ Whitebox.setInternalState(updateReporterCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(updateReporterCommand);
Command updateReporter = osgiContext.getService(Command.class);
assertTrue("Expected to retrieve the UpdateReporterCommand from the mocked OSGi environment.",
updateReporter instanceof UpdateReporterCommand);
- updateReporter.execute(new ExecutionContext(ExecutionContext.Mode.DRY_RUN, "42"));
+ updateReporter.run();
verify(logger).info("The following {} would be added to the Apache Reporter System:", "releases");
verify(logger).info(" - {}", "Apache Sling CLI 1");
verify(logger).info(" - {}", "Apache Sling CLI 2");
@@ -100,7 +106,12 @@ public class UpdateReporterCommandTest {
@Test
@PrepareForTest({UserInput.class})
public void testInteractive() throws Exception {
- osgiContext.registerInjectActivateService(new UpdateReporterCommand());
+ UpdateReporterCommand updateReporterCommand = spy(new UpdateReporterCommand());
+ Whitebox.setInternalState(updateReporterCommand, "repositoryId", 42);
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.INTERACTIVE);
+ Whitebox.setInternalState(updateReporterCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(updateReporterCommand);
Command updateReporter = osgiContext.getService(Command.class);
assertTrue("Expected to retrieve the UpdateReporterCommand from the mocked OSGi environment.",
updateReporter instanceof UpdateReporterCommand);
@@ -113,13 +124,18 @@ public class UpdateReporterCommandTest {
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
when(client.execute(any())).thenReturn(response);
- updateReporter.execute(new ExecutionContext(ExecutionContext.Mode.INTERACTIVE, "42"));
+ updateReporter.run();
verify(client, times(2)).execute(any());
}
@Test
public void testAuto() throws Exception {
- osgiContext.registerInjectActivateService(new UpdateReporterCommand());
+ UpdateReporterCommand updateReporterCommand = spy(new UpdateReporterCommand());
+ Whitebox.setInternalState(updateReporterCommand, "repositoryId", 42);
+ ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class);
+ Whitebox.setInternalState(reusableCLIOptions, "executionMode", ExecutionMode.AUTO);
+ Whitebox.setInternalState(updateReporterCommand, "reusableCLIOptions", reusableCLIOptions);
+ osgiContext.registerInjectActivateService(updateReporterCommand);
Command updateReporter = osgiContext.getService(Command.class);
assertTrue("Expected to retrieve the UpdateReporterCommand from the mocked OSGi environment.",
updateReporter instanceof UpdateReporterCommand);
@@ -128,7 +144,7 @@ public class UpdateReporterCommandTest {
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
when(client.execute(any())).thenReturn(response);
- updateReporter.execute(new ExecutionContext(ExecutionContext.Mode.AUTO, "42"));
+ updateReporter.run();
verify(client, times(2)).execute(any());
}