Basic zsh completion support
- This adds basic zsh support similarly to existing bash completion - New command "completion zsh" - Fix internal recursive command completion model for cases with deep nested commands - Fixes #927
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2022 the original author or authors.
|
* Copyright 2022-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,6 +20,7 @@ import org.springframework.shell.standard.AbstractShellComponent;
|
|||||||
import org.springframework.shell.standard.ShellComponent;
|
import org.springframework.shell.standard.ShellComponent;
|
||||||
import org.springframework.shell.standard.ShellMethod;
|
import org.springframework.shell.standard.ShellMethod;
|
||||||
import org.springframework.shell.standard.completion.BashCompletions;
|
import org.springframework.shell.standard.completion.BashCompletions;
|
||||||
|
import org.springframework.shell.standard.completion.ZshCompletions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to create a shell completion files, i.e. for {@code bash}.
|
* Command to create a shell completion files, i.e. for {@code bash}.
|
||||||
@@ -52,4 +53,10 @@ public class Completion extends AbstractShellComponent {
|
|||||||
BashCompletions bashCompletions = new BashCompletions(resourceLoader, getCommandCatalog());
|
BashCompletions bashCompletions = new BashCompletions(resourceLoader, getCommandCatalog());
|
||||||
return bashCompletions.generate(rootCommand);
|
return bashCompletions.generate(rootCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ShellMethod(key = "completion zsh", value = "Generate zsh completion script")
|
||||||
|
public String zsh() {
|
||||||
|
ZshCompletions zshCompletions = new ZshCompletions(resourceLoader, getCommandCatalog());
|
||||||
|
return zshCompletions.generate(rootCommand);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2022 the original author or authors.
|
* Copyright 2022-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -88,8 +88,9 @@ public abstract class AbstractCompletions {
|
|||||||
else {
|
else {
|
||||||
commandKey = splitKeys[i];
|
commandKey = splitKeys[i];
|
||||||
}
|
}
|
||||||
|
String desc = i + 1 < splitKeys.length ? null : registration.getDescription();
|
||||||
DefaultCommandModelCommand command = commands.computeIfAbsent(commandKey,
|
DefaultCommandModelCommand command = commands.computeIfAbsent(commandKey,
|
||||||
(fullCommand) -> new DefaultCommandModelCommand(fullCommand, main));
|
(fullCommand) -> new DefaultCommandModelCommand(fullCommand, main, desc));
|
||||||
|
|
||||||
// TODO long vs short
|
// TODO long vs short
|
||||||
List<CommandModelOption> options = registration.getOptions().stream()
|
List<CommandModelOption> options = registration.getOptions().stream()
|
||||||
@@ -147,8 +148,13 @@ public abstract class AbstractCompletions {
|
|||||||
interface CommandModelCommand {
|
interface CommandModelCommand {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets sub-commands known to this command.
|
* Gets a description of a command.
|
||||||
|
* @return command description
|
||||||
|
*/
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets sub-commands known to this command.
|
||||||
* @return known sub-commands
|
* @return known sub-commands
|
||||||
*/
|
*/
|
||||||
List<CommandModelCommand> getCommands();
|
List<CommandModelCommand> getCommands();
|
||||||
@@ -240,12 +246,19 @@ public abstract class AbstractCompletions {
|
|||||||
|
|
||||||
private String fullCommand;
|
private String fullCommand;
|
||||||
private String mainCommand;
|
private String mainCommand;
|
||||||
|
private String description;
|
||||||
private List<CommandModelCommand> commands = new ArrayList<>();
|
private List<CommandModelCommand> commands = new ArrayList<>();
|
||||||
private List<CommandModelOption> options = new ArrayList<>();
|
private List<CommandModelOption> options = new ArrayList<>();
|
||||||
|
|
||||||
DefaultCommandModelCommand(String fullCommand, String mainCommand) {
|
DefaultCommandModelCommand(String fullCommand, String mainCommand, String description) {
|
||||||
this.fullCommand = fullCommand;
|
this.fullCommand = fullCommand;
|
||||||
this.mainCommand = mainCommand;
|
this.mainCommand = mainCommand;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -293,6 +306,9 @@ public abstract class AbstractCompletions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void addCommand(DefaultCommandModelCommand command) {
|
void addCommand(DefaultCommandModelCommand command) {
|
||||||
|
if (commands.contains(command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
commands.add(command);
|
commands.add(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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.standard.completion;
|
||||||
|
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.shell.command.CommandCatalog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completion script generator for a {@code zsh}.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
public class ZshCompletions extends AbstractCompletions {
|
||||||
|
|
||||||
|
public ZshCompletions(ResourceLoader resourceLoader, CommandCatalog commandCatalog) {
|
||||||
|
super(resourceLoader, commandCatalog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generate(String rootCommand) {
|
||||||
|
CommandModel model = generateCommandModel();
|
||||||
|
return builder()
|
||||||
|
.attribute("name", rootCommand)
|
||||||
|
.attribute("model", model)
|
||||||
|
.group("classpath:completion/zsh.stg")
|
||||||
|
.appendGroup("main")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
89
spring-shell-standard/src/main/resources/completion/zsh.stg
Normal file
89
spring-shell-standard/src/main/resources/completion/zsh.stg
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//
|
||||||
|
// pre content template before commands
|
||||||
|
// needs to escape some > characters
|
||||||
|
//
|
||||||
|
pre(name) ::= <<
|
||||||
|
#compdef _<name> <name>
|
||||||
|
>>
|
||||||
|
|
||||||
|
//
|
||||||
|
// commands section with command and description
|
||||||
|
//
|
||||||
|
cmd_and_desc(command) ::= <<
|
||||||
|
"<command.mainCommand>:<command.description>"
|
||||||
|
>>
|
||||||
|
|
||||||
|
//
|
||||||
|
// case for command to call function
|
||||||
|
//
|
||||||
|
cmd_func(name,command) ::= <<
|
||||||
|
<command.mainCommand>)
|
||||||
|
_<name>_<command.commandParts:{p | <p>}; separator="_">
|
||||||
|
;;
|
||||||
|
>>
|
||||||
|
|
||||||
|
//
|
||||||
|
// recursive sub commands
|
||||||
|
//
|
||||||
|
sub_command(name,command,commands) ::= <<
|
||||||
|
function _<name>_<command.commandParts:{p | <p>}; separator="_"> {
|
||||||
|
local -a commands
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
<command.flags:{f | "<f>" \\}; separator="\n">
|
||||||
|
"1: :->cmnds" \
|
||||||
|
"*::arg:->args"
|
||||||
|
|
||||||
|
case $state in
|
||||||
|
cmnds)
|
||||||
|
commands=(
|
||||||
|
<commands:{c | <cmd_and_desc(c)>}; separator="\n">
|
||||||
|
)
|
||||||
|
_describe "command" commands
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$words[1]" in
|
||||||
|
<commands:{c | <cmd_func(name,c)>}; separator="\n">
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
<commands:{c | <sub_command(name,c,c.commands)>}; separator="\n\n">
|
||||||
|
>>
|
||||||
|
|
||||||
|
//
|
||||||
|
// top level commands
|
||||||
|
//
|
||||||
|
top_commands(name,commands) ::= <<
|
||||||
|
function _<name> {
|
||||||
|
local -a commands
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
"1: :->cmnds" \
|
||||||
|
"*::arg:->args"
|
||||||
|
|
||||||
|
case $state in
|
||||||
|
cmnds)
|
||||||
|
commands=(
|
||||||
|
<commands:{c | <cmd_and_desc(c)>}; separator="\n">
|
||||||
|
)
|
||||||
|
_describe "command" commands
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$words[1]" in
|
||||||
|
<commands:{c | <cmd_func(name,c)>}; separator="\n">
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
<commands:{c | <sub_command(name,c,c.commands)>}; separator="\n\n">
|
||||||
|
>>
|
||||||
|
|
||||||
|
//
|
||||||
|
// main template to call from render
|
||||||
|
//
|
||||||
|
main(name, model) ::= <<
|
||||||
|
<pre(name)>
|
||||||
|
|
||||||
|
<top_commands(name,model.commands)>
|
||||||
|
>>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2022 the original author or authors.
|
* Copyright 2022-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -29,73 +29,224 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
public class AbstractCompletionsTests {
|
public class AbstractCompletionsTests {
|
||||||
|
|
||||||
|
private final TestCommands commands = new TestCommands();
|
||||||
|
|
||||||
|
private final CommandRegistration r1 = CommandRegistration.builder()
|
||||||
|
.command("test1")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test1")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param1")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r2 = CommandRegistration.builder()
|
||||||
|
.command("test2")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test2")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3 = CommandRegistration.builder()
|
||||||
|
.command("test3")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test3")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_4 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test4")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_5 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test5")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_4_5 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test4", "test5")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_4_6 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test4", "test6")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_5_5 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test5", "test5")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final CommandRegistration r3_5_6 = CommandRegistration.builder()
|
||||||
|
.command("test3", "test5", "test6")
|
||||||
|
.withTarget()
|
||||||
|
.method(commands, "test4")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("param4")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deepL3Commands() {
|
||||||
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
|
||||||
|
commandCatalog.register(r3_4_5);
|
||||||
|
commandCatalog.register(r3_4_6);
|
||||||
|
commandCatalog.register(r3_5_5);
|
||||||
|
commandCatalog.register(r3_5_6);
|
||||||
|
TestCompletions completions = new TestCompletions(resourceLoader, commandCatalog);
|
||||||
|
CommandModel commandModel = completions.testCommandModel();
|
||||||
|
|
||||||
|
assertThat(commandModel.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c3 -> {
|
||||||
|
assertThat(c3.getMainCommand()).isEqualTo("test3");
|
||||||
|
assertThat(c3.getOptions()).hasSize(0);
|
||||||
|
assertThat(c3.getSubCommands()).hasSize(2);
|
||||||
|
assertThat(c3.getCommands()).hasSize(2);
|
||||||
|
assertThat(c3.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c34 -> {
|
||||||
|
assertThat(c34.getMainCommand()).isEqualTo("test4");
|
||||||
|
assertThat(c34.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c345 -> {
|
||||||
|
assertThat(c345.getMainCommand()).isEqualTo("test5");
|
||||||
|
},
|
||||||
|
c346 -> {
|
||||||
|
assertThat(c346.getMainCommand()).isEqualTo("test6");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
c35 -> {
|
||||||
|
assertThat(c35.getMainCommand()).isEqualTo("test5");
|
||||||
|
assertThat(c35.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c355 -> {
|
||||||
|
assertThat(c355.getMainCommand()).isEqualTo("test5");
|
||||||
|
},
|
||||||
|
c356 -> {
|
||||||
|
assertThat(c356.getMainCommand()).isEqualTo("test6");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deepL2Commands() {
|
||||||
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
|
||||||
|
commandCatalog.register(r3_4);
|
||||||
|
commandCatalog.register(r3_5);
|
||||||
|
TestCompletions completions = new TestCompletions(resourceLoader, commandCatalog);
|
||||||
|
CommandModel commandModel = completions.testCommandModel();
|
||||||
|
|
||||||
|
assertThat(commandModel.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c3 -> {
|
||||||
|
assertThat(c3.getMainCommand()).isEqualTo("test3");
|
||||||
|
assertThat(c3.getOptions()).hasSize(0);
|
||||||
|
assertThat(c3.getSubCommands()).hasSize(2);
|
||||||
|
assertThat(c3.getCommands()).hasSize(2);
|
||||||
|
assertThat(c3.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
|
c34 -> {
|
||||||
|
assertThat(c34.getMainCommand()).isEqualTo("test4");
|
||||||
|
assertThat(c34.getOptions()).hasSize(1);
|
||||||
|
assertThat(c34.getOptions()).satisfiesExactly(
|
||||||
|
o -> {
|
||||||
|
assertThat(o.option()).isEqualTo("--param4");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
c35 -> {
|
||||||
|
assertThat(c35.getMainCommand()).isEqualTo("test5");
|
||||||
|
assertThat(c35.getOptions()).hasSize(1);
|
||||||
|
assertThat(c35.getOptions()).satisfiesExactly(
|
||||||
|
o -> {
|
||||||
|
assertThat(o.option()).isEqualTo("--param4");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicModelGeneration() {
|
public void testBasicModelGeneration() {
|
||||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
CommandCatalog commandCatalog = CommandCatalog.of();
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
|
||||||
TestCommands commands = new TestCommands();
|
commandCatalog.register(r1);
|
||||||
|
commandCatalog.register(r2);
|
||||||
CommandRegistration registration1 = CommandRegistration.builder()
|
commandCatalog.register(r3);
|
||||||
.command("test1")
|
commandCatalog.register(r3_4);
|
||||||
.withTarget()
|
|
||||||
.method(commands, "test1")
|
|
||||||
.and()
|
|
||||||
.withOption()
|
|
||||||
.longNames("param1")
|
|
||||||
.and()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
CommandRegistration registration2 = CommandRegistration.builder()
|
|
||||||
.command("test2")
|
|
||||||
.withTarget()
|
|
||||||
.method(commands, "test2")
|
|
||||||
.and()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
CommandRegistration registration3 = CommandRegistration.builder()
|
|
||||||
.command("test3")
|
|
||||||
.withTarget()
|
|
||||||
.method(commands, "test3")
|
|
||||||
.and()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
CommandRegistration registration4 = CommandRegistration.builder()
|
|
||||||
.command("test3", "test4")
|
|
||||||
.withTarget()
|
|
||||||
.method(commands, "test4")
|
|
||||||
.and()
|
|
||||||
.withOption()
|
|
||||||
.longNames("param4")
|
|
||||||
.and()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
commandCatalog.register(registration1);
|
|
||||||
commandCatalog.register(registration2);
|
|
||||||
commandCatalog.register(registration3);
|
|
||||||
commandCatalog.register(registration4);
|
|
||||||
|
|
||||||
TestCompletions completions = new TestCompletions(resourceLoader, commandCatalog);
|
TestCompletions completions = new TestCompletions(resourceLoader, commandCatalog);
|
||||||
CommandModel commandModel = completions.testCommandModel();
|
CommandModel commandModel = completions.testCommandModel();
|
||||||
assertThat(commandModel.getCommands()).hasSize(3);
|
assertThat(commandModel.getCommands()).satisfiesExactlyInAnyOrder(
|
||||||
assertThat(commandModel.getCommands().stream().map(c -> c.getMainCommand())).containsExactlyInAnyOrder("test1", "test2",
|
c1 -> {
|
||||||
"test3");
|
assertThat(c1.getMainCommand()).isEqualTo("test1");
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test1")).findFirst().get()
|
assertThat(c1.getSubCommands()).hasSize(0);
|
||||||
.getOptions()).hasSize(1);
|
assertThat(c1.getOptions()).hasSize(1);
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test1")).findFirst().get()
|
assertThat(c1.getOptions()).satisfiesExactly(
|
||||||
.getOptions().get(0).option()).isEqualTo("--param1");
|
o -> {
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test2")).findFirst().get()
|
assertThat(o.option()).isEqualTo("--param1");
|
||||||
.getOptions()).hasSize(0);
|
}
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test3")).findFirst().get()
|
);
|
||||||
.getOptions()).hasSize(0);
|
},
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test3")).findFirst().get()
|
c2 -> {
|
||||||
.getCommands()).hasSize(1);
|
assertThat(c2.getMainCommand()).isEqualTo("test2");
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test3")).findFirst().get()
|
assertThat(c2.getSubCommands()).hasSize(0);
|
||||||
.getCommands().get(0).getMainCommand()).isEqualTo("test4");
|
assertThat(c2.getOptions()).hasSize(0);
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test3")).findFirst().get()
|
},
|
||||||
.getCommands().get(0).getOptions()).hasSize(1);
|
c3 -> {
|
||||||
assertThat(commandModel.getCommands().stream().filter(c -> c.getMainCommand().equals("test3")).findFirst().get()
|
assertThat(c3.getMainCommand()).isEqualTo("test3");
|
||||||
.getCommands().get(0).getOptions().get(0).option()).isEqualTo("--param4");
|
assertThat(c3.getOptions()).hasSize(0);
|
||||||
|
assertThat(c3.getSubCommands()).hasSize(1);
|
||||||
|
assertThat(c3.getCommands()).hasSize(1);
|
||||||
|
assertThat(c3.getCommands()).satisfiesExactly(
|
||||||
|
c34 -> {
|
||||||
|
assertThat(c34.getMainCommand()).isEqualTo("test4");
|
||||||
|
assertThat(c34.getOptions()).hasSize(1);
|
||||||
|
assertThat(c34.getOptions()).satisfiesExactly(
|
||||||
|
o -> {
|
||||||
|
assertThat(o.option()).isEqualTo("--param4");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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.standard.completion;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.shell.command.CommandCatalog;
|
||||||
|
import org.springframework.shell.command.CommandContext;
|
||||||
|
import org.springframework.shell.command.CommandRegistration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class ZshCompletionsTests {
|
||||||
|
|
||||||
|
AnnotationConfigApplicationContext context;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
context = new AnnotationConfigApplicationContext();
|
||||||
|
context.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void clean() {
|
||||||
|
if (context != null) {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
context = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoCommands() {
|
||||||
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
ZshCompletions completions = new ZshCompletions(context, commandCatalog);
|
||||||
|
String zsh = completions.generate("root-command");
|
||||||
|
assertThat(zsh).contains("root-command");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommandFromMethod() {
|
||||||
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
registerFromMethod(commandCatalog);
|
||||||
|
ZshCompletions completions = new ZshCompletions(context, commandCatalog);
|
||||||
|
String zsh = completions.generate("root-command");
|
||||||
|
assertThat(zsh).contains("root-command");
|
||||||
|
assertThat(zsh).contains("testmethod1)");
|
||||||
|
assertThat(zsh).contains("_root-command_testmethod1");
|
||||||
|
assertThat(zsh).contains("--arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommandFromFunction() {
|
||||||
|
CommandCatalog commandCatalog = CommandCatalog.of();
|
||||||
|
registerFromFunction(commandCatalog, "testmethod1");
|
||||||
|
ZshCompletions completions = new ZshCompletions(context, commandCatalog);
|
||||||
|
String zsh = completions.generate("root-command");
|
||||||
|
assertThat(zsh).contains("root-command");
|
||||||
|
assertThat(zsh).contains("testmethod1)");
|
||||||
|
assertThat(zsh).contains("_root-command_testmethod1");
|
||||||
|
assertThat(zsh).contains("--arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFromMethod(CommandCatalog commandCatalog) {
|
||||||
|
Pojo1 pojo1 = new Pojo1();
|
||||||
|
CommandRegistration registration = CommandRegistration.builder()
|
||||||
|
.command("testmethod1")
|
||||||
|
.description("desc")
|
||||||
|
.withTarget()
|
||||||
|
.method(pojo1, "method1")
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("arg1")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
commandCatalog.register(registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFromFunction(CommandCatalog commandCatalog, String command) {
|
||||||
|
Function<CommandContext, String> function = ctx -> {
|
||||||
|
String arg1 = ctx.getOptionValue("arg1");
|
||||||
|
return String.format("hi, arg1 value is '%s'", arg1);
|
||||||
|
};
|
||||||
|
CommandRegistration registration = CommandRegistration.builder()
|
||||||
|
.command(command)
|
||||||
|
.withTarget()
|
||||||
|
.function(function)
|
||||||
|
.and()
|
||||||
|
.withOption()
|
||||||
|
.longNames("arg1")
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
commandCatalog.register(registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Pojo1 {
|
||||||
|
|
||||||
|
void method1() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user