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]]
= 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. 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 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 Spring Shell lets you do that, even letting you provide a short explanation of the reason for
the command not being available. 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. There are three possible ways for a command to indicate availability.
They all use a no-arg method that returns an instance of `Availability`. They all use a no-arg method that returns an instance of `Availability`.
Consider the following example: Consider the following example:
[source, java] [source, java, indent=0]
---- ----
@ShellComponent include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-in-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");
}
}
---- ----
The `connect` method is used to connect to the server (details omitted), altering the state 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 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: 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 <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, 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` 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: 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.") include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-name-multi-in-shellcomponent]
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");
}
---- ----
[TIP] [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 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: with a single availability method:
[source,java] [source, java, indent=0]
---- ----
@ShellComponent include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-default-value-in-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() {}
}
---- ----
===== =====
TIP: Spring Shell does not impose many constraints on how to write commands and how to organize classes. 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[]
}
}