Document command availability

- Docs for annotation and registration
- Remove dynamic from header
- Move samples to snippets
- Fixes #1053
This commit is contained in:
Janne Valkealahti
2024-05-23 09:09:03 +01:00
parent b954211d01
commit 80ec92ea84
2 changed files with 231 additions and 69 deletions

View File

@@ -1,5 +1,7 @@
[[dynamic-command-availability]]
= Dynamic Command Availability
= Command Availability
ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs]
Registered commands do not always make sense, due to the internal state of the application.
For example, there may be a `download` command, but it only works once the user has used `connect` on a remote
@@ -8,34 +10,35 @@ the command exists but that it is not available at the time.
Spring Shell lets you do that, even letting you provide a short explanation of the reason for
the command not being available.
== Programmatic
With programmatic registration you can use `availability` method which takes
`Supplier<Availability>`.
[source, java, indent=0]
----
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-programmatic]
----
== Annotation
With annotation based commands you can use `@CommandAvailability` together with
`AvailabilityProvider`.
[source, java, indent=0]
----
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-annotation]
----
== Legacy Annotation
There are three possible ways for a command to indicate availability.
They all use a no-arg method that returns an instance of `Availability`.
Consider the following example:
[source, java]
[source, java, indent=0]
----
@ShellComponent
public class MyCommands {
private boolean connected;
@ShellMethod("Connect to the server.")
public void connect(String user, String password) {
[...]
connected = true;
}
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
public Availability downloadAvailability() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-in-shellcomponent]
----
The `connect` method is used to connect to the server (details omitted), altering the state
@@ -65,20 +68,11 @@ You should not start the sentence with a capital or add a final period
If naming the availability method after the name of the command method does not suit you, you
can provide an explicit name by using the `@ShellMethodAvailability` annotation:
[source, java]
[source, java, indent=0]
----
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-name-in-shellcomponent]
----
@ShellMethod("Download the nuclear codes.")
@ShellMethodAvailability("availabilityCheck") // <1>
public void download() {
[...]
}
public Availability availabilityCheck() { // <1>
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
----
<1> the names have to match
Finally, it is often the case that several commands in the same class share the same internal state and, thus,
@@ -86,24 +80,9 @@ should all be available or unavailable as a group. Instead of having to stick th
on all command methods, Spring Shell lets you flip things around and put the `@ShellMethodAvailabilty`
annotation on the availability method, specifying the names of the commands that it controls:
[source, java]
[source, java, indent=0]
----
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
@ShellMethod("Disconnect from the server.")
public void disconnect() {
[...]
}
@ShellMethodAvailability({"download", "disconnect"})
public Availability availabilityCheck() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-name-multi-in-shellcomponent]
----
[TIP]
@@ -112,24 +91,11 @@ The default value for the `@ShellMethodAvailability.value()` attribute is `*`. T
wildcard matches all command names. This makes it easy to turn all commands of a single class on or off
with a single availability method:
[source,java]
[source, java, indent=0]
----
@ShellComponent
public class Toggles {
@ShellMethodAvailability
public Availability availabilityOnWeekdays() {
return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
? Availability.available()
: Availability.unavailable("today is not Sunday");
}
@ShellMethod
public void foo() {}
@ShellMethod
public void bar() {}
}
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-default-value-in-shellcomponent]
----
=====
TIP: Spring Shell does not impose many constraints on how to write commands and how to organize classes.

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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.springframework.shell.docs;
import java.util.Calendar;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.Availability;
import org.springframework.shell.AvailabilityProvider;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.command.annotation.CommandAvailability;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellMethodAvailability;
import static java.util.Calendar.DAY_OF_WEEK;
import static java.util.Calendar.SUNDAY;
class CommandAvailabilitySnippets {
class Dump1 {
// tag::availability-method-in-shellcomponent[]
@ShellComponent
public class MyCommands {
private boolean connected;
@ShellMethod("Connect to the server.")
public void connect(String user, String password) {
// do something
connected = true;
}
@ShellMethod("Download the nuclear codes.")
public void download() {
// do something
}
public Availability downloadAvailability() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
// end::availability-method-in-shellcomponent[]
}
class Dump2 {
boolean connected;
// tag::availability-method-name-in-shellcomponent[]
@ShellMethod("Download the nuclear codes.")
@ShellMethodAvailability("availabilityCheck") // <1>
public void download() {
}
public Availability availabilityCheck() { // <1>
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
// end::availability-method-name-in-shellcomponent[]
}
class Dump3 {
boolean connected;
// tag::availability-method-name-multi-in-shellcomponent[]
@ShellMethod("Download the nuclear codes.")
public void download() {
}
@ShellMethod("Disconnect from the server.")
public void disconnect() {
}
@ShellMethodAvailability({"download", "disconnect"})
public Availability availabilityCheck() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
// end::availability-method-name-multi-in-shellcomponent[]
}
// tag::availability-method-default-value-in-shellcomponent[]
@ShellComponent
public class Toggles {
@ShellMethodAvailability
public Availability availabilityOnWeekdays() {
return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
? Availability.available()
: Availability.unavailable("today is not Sunday");
}
@ShellMethod
public void foo() {}
@ShellMethod
public void bar() {}
}
// end::availability-method-default-value-in-shellcomponent[]
class Dump4 {
// tag::availability-method-annotation[]
@Command
class MyCommands {
private boolean connected;
@Command(command = "connect")
public void connect(String user, String password) {
connected = true;
}
@Command(command = "download")
@CommandAvailability(provider = "downloadAvailability")
public void download(
) {
// do something
}
@Bean
public AvailabilityProvider downloadAvailability() {
return () -> connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
// end::availability-method-annotation[]
}
class Dump5 {
// tag::availability-method-programmatic[]
private boolean connected;
@Bean
public CommandRegistration connect(
CommandRegistration.BuilderSupplier builder) {
return builder.get()
.command("connect")
.withOption()
.longNames("connected")
.required()
.type(boolean.class)
.and()
.withTarget()
.consumer(ctx -> {
boolean connected = ctx.getOptionValue("connected");
this.connected = connected;
})
.and()
.build();
}
@Bean
public CommandRegistration download(
CommandRegistration.BuilderSupplier builder) {
return builder.get()
.command("download")
.availability(() -> {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
})
.withTarget()
.consumer(ctx -> {
// do something
})
.and()
.build();
}
// end::availability-method-programmatic[]
}
}