Skip to content

Completion command for bash #344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<properties>
<jline.version>3.21.0</jline.version>
<jcommander.version>1.81</jcommander.version>
<antlr-st4.version>4.3.1</antlr-st4.version>
</properties>

<modules>
Expand Down Expand Up @@ -98,6 +99,11 @@
<version>${jcommander.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>${antlr-st4.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 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.
Expand Down Expand Up @@ -179,6 +179,28 @@ public void setEnabled(boolean enabled) {
}
}

public static class CompletionCommand {

private boolean enabled = true;
private String rootCommand;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getRootCommand() {
return rootCommand;
}

public void setRootCommand(String rootCommand) {
this.rootCommand = rootCommand;
}
}

public static class Command {

private HelpCommand help = new HelpCommand();
Expand All @@ -187,6 +209,7 @@ public static class Command {
private StacktraceCommand stacktrace = new StacktraceCommand();
private ScriptCommand script = new ScriptCommand();
private HistoryCommand history = new HistoryCommand();
private CompletionCommand completion = new CompletionCommand();

public void setHelp(HelpCommand help) {
this.help = help;
Expand Down Expand Up @@ -235,5 +258,13 @@ public HistoryCommand getHistory() {
public void setHistory(HistoryCommand history) {
this.history = history;
}

public CompletionCommand getCompletion() {
return completion;
}

public void setCompletion(CompletionCommand completion) {
this.completion = completion;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 the original author or authors.
* Copyright 2017-2022 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.
Expand All @@ -22,10 +22,14 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.boot.condition.OnCompletionCommandCondition;
import org.springframework.shell.result.ThrowableResultHandler;
import org.springframework.shell.standard.commands.Clear;
import org.springframework.shell.standard.commands.Completion;
import org.springframework.shell.standard.commands.Help;
import org.springframework.shell.standard.commands.History;
import org.springframework.shell.standard.commands.Quit;
Expand All @@ -39,6 +43,7 @@
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Help.Command.class })
@EnableConfigurationProperties(SpringShellProperties.class)
public class StandardCommandsAutoConfiguration {

@Bean
Expand Down Expand Up @@ -82,4 +87,11 @@ public Script script(Parser parser) {
public History historyCommand(org.jline.reader.History jLineHistory) {
return new History(jLineHistory);
}

@Bean
@ConditionalOnMissingBean(Completion.Command.class)
@Conditional(OnCompletionCommandCondition.class)
public Completion completion(SpringShellProperties properties) {
return new Completion(properties.getCommand().getCompletion().getRootCommand());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 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.boot.condition;

import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

public class OnCompletionCommandCondition extends AllNestedConditions {

public OnCompletionCommandCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnProperty(prefix = "spring.shell.command.completion", value = "root-command")
static class RootNameCondition {
}

@ConditionalOnProperty(prefix = "spring.shell.command.completion", value = "enabled", havingValue = "true", matchIfMissing = true)
static class EnabledCondition {
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 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.
Expand All @@ -15,15 +15,10 @@
*/
package org.springframework.shell.boot;

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -46,26 +41,25 @@ public void defaultNoPropertiesSet() {
assertThat(properties.getCommand().getQuit().isEnabled()).isTrue();
assertThat(properties.getCommand().getScript().isEnabled()).isTrue();
assertThat(properties.getCommand().getStacktrace().isEnabled()).isTrue();
assertThat(properties.getCommand().getCompletion().isEnabled()).isTrue();
assertThat(properties.getCommand().getCompletion().getRootCommand()).isNull();
});
}

@Test
public void setProperties() {
this.contextRunner
.withInitializer(context -> {
Map<String, Object> map = new HashMap<>();
map.put("spring.shell.script.enabled", "false");
map.put("spring.shell.interactive.enabled", "false");
map.put("spring.shell.noninteractive.enabled", "false");
map.put("spring.shell.command.clear.enabled", "false");
map.put("spring.shell.command.help.enabled", "false");
map.put("spring.shell.command.history.enabled", "false");
map.put("spring.shell.command.quit.enabled", "false");
map.put("spring.shell.command.script.enabled", "false");
map.put("spring.shell.command.stacktrace.enabled", "false");
context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));
})
.withPropertyValues("spring.shell.script.enabled=false")
.withPropertyValues("spring.shell.interactive.enabled=false")
.withPropertyValues("spring.shell.noninteractive.enabled=false")
.withPropertyValues("spring.shell.command.clear.enabled=false")
.withPropertyValues("spring.shell.command.help.enabled=false")
.withPropertyValues("spring.shell.command.history.enabled=false")
.withPropertyValues("spring.shell.command.quit.enabled=false")
.withPropertyValues("spring.shell.command.script.enabled=false")
.withPropertyValues("spring.shell.command.stacktrace.enabled=false")
.withPropertyValues("spring.shell.command.completion.enabled=false")
.withPropertyValues("spring.shell.command.completion.root-command=fake")
.withUserConfiguration(Config1.class)
.run((context) -> {
SpringShellProperties properties = context.getBean(SpringShellProperties.class);
Expand All @@ -78,6 +72,8 @@ public void setProperties() {
assertThat(properties.getCommand().getQuit().isEnabled()).isFalse();
assertThat(properties.getCommand().getScript().isEnabled()).isFalse();
assertThat(properties.getCommand().getStacktrace().isEnabled()).isFalse();
assertThat(properties.getCommand().getCompletion().isEnabled()).isFalse();
assertThat(properties.getCommand().getCompletion().getRootCommand()).isEqualTo("fake");
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2022 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.boot;

import java.util.function.Function;

import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.shell.standard.commands.Completion;

import static org.assertj.core.api.Assertions.assertThat;

public class StandardCommandsAutoConfigurationTests {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(StandardCommandsAutoConfiguration.class));

@Test
public void testCompletionCommand() {
this.contextRunner
.with(disableCommands("help", "clear", "quit", "stacktrace", "script", "history"))
.run((context) -> {assertThat(context).doesNotHaveBean(Completion.class);
});
this.contextRunner
.with(disableCommands("help", "clear", "quit", "stacktrace", "script", "history", "completion"))
.withPropertyValues("spring.shell.command.completion.root-command=fake")
.run((context) -> {assertThat(context).doesNotHaveBean(Completion.class);
});
this.contextRunner
.with(disableCommands("help", "clear", "quit", "stacktrace", "script", "history"))
.withPropertyValues("spring.shell.command.completion.root-command=fake")
.run((context) -> {assertThat(context).hasSingleBean(Completion.class);
});
}

private static Function<ApplicationContextRunner, ApplicationContextRunner> disableCommands(String... commands) {
return (cr) -> {
for (String command : commands) {
cr = cr.withPropertyValues(String.format("spring.shell.command.%s.enabled=false", command));
}
return cr;
};
}
}
7 changes: 7 additions & 0 deletions spring-shell-samples/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
logging:
level:
root: error
spring:
main:
banner-mode: off
shell:
command:
completion:
root-command: spring-shell-samples
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2022 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.commands;

import java.util.stream.Collectors;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.completion.BashCompletions;

/**
* Command to create a shell completion files, i.e. for {@code bash}.
*
* @author Janne Valkealahti
*/
@ShellComponent
public class Completion extends AbstractShellComponent implements ResourceLoaderAware {

/**
* Marker interface used in auto-config.
*/
public interface Command {
}

private ResourceLoader resourceLoader;
private String rootCommand;

public Completion(String rootCommand) {
this.rootCommand = rootCommand;
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

@ShellMethod(key = "completion bash", value = "Generate bash completion script")
public String bash() {
BashCompletions bashCompletions = new BashCompletions(resourceLoader, getCommandRegistry(),
getParameterResolver().collect(Collectors.toList()));
return bashCompletions.generate(rootCommand);
}
}
4 changes: 4 additions & 0 deletions spring-shell-standard/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-core</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Loading