Skip to content

Commit

Permalink
⬆️ Bump spring-shell-starter from 2.1.0 to 2.1.1 (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonimus authored Aug 20, 2022
1 parent 7e8f883 commit c7a3335
Show file tree
Hide file tree
Showing 5 changed files with 16 additions and 150 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,6 @@ ssh:
authorized-roles:
- ADMIN
display-banner: true
# to use ExtendedFileValueProviderTest instead of spring shell FileValueProvider for all File option parameters
# if set to false, it still can be used via '@ShellOption(valueProvider = ExtendedFileValueProviderTest.class) File file'
extended-file-provider: true
history-file: <java.io.tmpdir>/sshShellHistory.log
# since 1.3.0, set to false to have one file per user (<history-directory>/sshShellHistory-<user>.log)
shared-history: true
Expand Down Expand Up @@ -310,8 +307,8 @@ It also provides a ``setTaskScheduler()`` in case you want to specify custom one
| ts1**, ** | |
| ts2** in context | Local single-threaded (could not find name ** |
| taskScheduler**) | |
| Multiple ``TaskScheduler`` beans named **
taskScheduler**, ** | |
| Multiple ``TaskScheduler`` beans named ** | |
| taskScheduler**, ** | |
| ts2**, ** | |
| ts3** in context | ** |
| taskScheduler** bean | |
Expand Down Expand Up @@ -798,6 +795,11 @@ public class ApplicationTest {

## Release notes

### 2.0.1

* Bump spring-boot from 2.7.2 to 2.7.3
* Bump spring shell from 2.1.0 to 2.1.1

### 2.0.0

* Bump spring-boot from 2.5.6 to 2.7.2
Expand Down Expand Up @@ -951,9 +953,8 @@ autoconfiguration will still create its own built in commands that you can deact
### 1.2.0

* Bump to spring boot 2.2.0.RELEASE
* Audit and Http Trace actuator commands will be disabled by default, because endpoint will be by spring boot by
default
(
* `Audit` and `Http Trace` actuator commands will be disabled by default, because endpoint will be by spring boot by
default (
check [spring boot migration 2.2](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes#actuator-http-trace-and-auditing-are-disabled-by-default)
for more info)
* Fix hanging terminal when unexpected runtime exception occurs
Expand All @@ -971,7 +972,7 @@ autoconfiguration will still create its own built in commands that you can deact

### 1.1.4

* [AnyOsFileValueProvider.java](./starter/src/main/java/com/github/fonimus/ssh/shell/providers/ExtendedFileValueProvider.java)
* [ExtendedFileValueProvider.java](./starter/src/main/java/com/github/fonimus/ssh/shell/providers/ExtendedFileValueProvider.java)
replaces `FileValueProvider` (spring shell default) by default
* Support Windows OS in addition to Unix
* Can be deactivated by `ssh.shell.any-os-file-provider`
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--dependencies-->
<spring-boot.version>2.7.3</spring-boot.version>
<spring-shell.version>2.1.0</spring-shell.version>
<spring-shell.version>2.1.1</spring-shell.version>
<sshd.version>2.9.0</sshd.version>
<junit-jupiter.version>5.9.0</junit-jupiter.version>
<!--sonar-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public void progress(int progress) {
*/
@ShellMethod("File command")
public void file(
@ShellOption(defaultValue = ShellOption.NULL) File file,
@ShellOption(valueProvider = FileValueProvider.class, defaultValue = ShellOption.NULL) File file,
@ShellOption(valueProvider = ExtendedFileValueProvider.class, defaultValue = ShellOption.NULL) File extended
) {
info(file);
Expand Down
137 changes: 1 addition & 136 deletions starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,15 @@
import org.springframework.context.annotation.Primary;
import org.springframework.shell.*;
import org.springframework.shell.command.CommandCatalog;
import org.springframework.shell.command.CommandOption;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.completion.CompletionResolver;
import org.springframework.shell.context.ShellContext;
import org.springframework.shell.exit.ExitCodeMappings;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static com.github.fonimus.ssh.shell.ExtendedInput.*;
import static com.github.fonimus.ssh.shell.SshShellCommandFactory.SSH_THREAD_CONTEXT;
Expand All @@ -52,7 +46,6 @@
public class ExtendedShell extends Shell {

private final ResultHandlerService resultHandlerService;
private final CommandCatalog commandRegistry;
private final List<String> postProcessorNames = new ArrayList<>();

/**
Expand All @@ -72,7 +65,6 @@ public ExtendedShell(
) {
super(resultHandlerService, commandRegistry, terminal, shellContext, exitCodeMappings);
this.resultHandlerService = resultHandlerService;
this.commandRegistry = commandRegistry;
if (postProcessors != null) {
this.postProcessorNames.addAll(postProcessors.stream().map(PostProcessor::getName).collect(Collectors.toList()));
}
Expand Down Expand Up @@ -158,53 +150,7 @@ public List<CompletionProposal> complete(CompletionContext context) {
if (context.getWords().contains("|")) {
return postProcessorNames.stream().map(CompletionProposal::new).collect(Collectors.toList());
}

String prefix = context.upToCursor();

List<CompletionProposal> candidates = new ArrayList<>(duplicatedCommandsStartingWith(prefix));

String best = duplicatedFindLongestCommand(prefix);
if (best != null) {
context = context.drop(best.split(" ").length);
CommandRegistration registration = commandRegistry.getRegistrations().get(best);
CompletionContext argsContext = context.commandRegistration(registration);

final List<String> words = context.getWords().stream().filter(StringUtils::hasText).collect(Collectors.toList());
String lastNotEmptyWord = words.isEmpty() ? null : words.get(words.size() - 1);

List<CommandOption> matchedArgOptions = new ArrayList<>();
if (lastNotEmptyWord != null) {
// last word used instead of first to check if matching args
matchedArgOptions.addAll(duplicatedMatchOptions(registration.getOptions(), lastNotEmptyWord));
}
if (matchedArgOptions.isEmpty()) {
// only add command options if last word did not match option
for (CompletionResolver resolver : completionResolvers) {
List<CompletionProposal> resolved = resolver.apply(argsContext);
candidates.addAll(resolved.stream().filter(cp -> !words.contains(cp.value())).collect(Collectors.toList()));
}
// try to check if previous word before last word is an option and last word is not empty
String lastOption = words.isEmpty() || words.size() < 2 ? null : words.get(words.size() - 2);
String lastWord = context.getWords().isEmpty() ? null : context.getWords().get(context.getWords().size() - 1);
if (lastOption != null && StringUtils.hasText(lastWord)) {
matchedArgOptions.addAll(duplicatedMatchOptions(registration.getOptions(), lastOption));
}
}

List<CompletionProposal> argProposals = matchedArgOptions.stream()
.flatMap(o -> {
Function<CompletionContext, List<CompletionProposal>> completion = o.getCompletion();
if (completion != null) {
List<CompletionProposal> apply = completion.apply(argsContext.commandOption(o));
return apply.stream();
}
return Stream.empty();
})
.collect(Collectors.toList());

candidates.addAll(argProposals);
}
return candidates;
return super.complete(context);
}

private static boolean isKeyCharInList(List<String> strList) {
Expand All @@ -216,87 +162,6 @@ private static boolean isKeyCharInList(List<String> strList) {
return false;
}

//---------------------------------
// Private methods from Shell
//---------------------------------

private List<CommandOption> duplicatedMatchOptions(List<CommandOption> options, String arg) {
List<CommandOption> matched = new ArrayList<>();
String trimmed = StringUtils.trimLeadingCharacter(arg, '-');
int count = arg.length() - trimmed.length();
if (count == 1) {
if (trimmed.length() == 1) {
Character trimmedChar = trimmed.charAt(0);
options.stream()
.filter(o -> {
for (Character sn : o.getShortNames()) {
if (trimmedChar.equals(sn)) {
return true;
}
}
return false;
})
.findFirst()
.ifPresent(matched::add);
} else if (trimmed.length() > 1) {
trimmed.chars().mapToObj(i -> (char) i)
.forEach(c -> options.forEach(o -> {
for (Character sn : o.getShortNames()) {
if (c.equals(sn)) {
matched.add(o);
}
}
}));
}
} else if (count == 2) {
options.stream()
.filter(o -> {
for (String ln : o.getLongNames()) {
if (trimmed.equals(ln)) {
return true;
}
}
return false;
})
.findFirst()
.ifPresent(matched::add);
}
return matched;
}

private List<CompletionProposal> duplicatedCommandsStartingWith(String prefix) {
// Workaround for https://github.com/spring-projects/spring-shell/issues/150
// (sadly, this ties this class to JLine somehow)
int lastWordStart = prefix.lastIndexOf(' ') + 1;
return commandRegistry.getRegistrations().entrySet().stream()
.filter(e -> e.getKey().startsWith(prefix))
.map(e -> {
String c = e.getKey();
c = c.substring(lastWordStart);
return duplicatedToCommandProposal(c, e.getValue());
})
.collect(Collectors.toList());
}

private CompletionProposal duplicatedToCommandProposal(String command, CommandRegistration registration) {
return new CompletionProposal(command)
.dontQuote(true)
.category("Available commands")
.description(registration.getDescription());
}

/**
* Returns the longest command that can be matched as first word(s) in the given buffer.
*
* @return a valid command name, or {@literal null} if none matched
*/
private String duplicatedFindLongestCommand(String prefix) {
String result = commandRegistry.getRegistrations().keySet().stream()
.filter(command -> prefix.equals(command) || prefix.startsWith(command + " "))
.reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
return "".equals(result) ? null : result;
}

/**
* Shell notifier interface
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ private static List<Arguments> completeSource() {
Arguments.of("cmd --a", Arrays.asList("--ab", "--ac")),
Arguments.of("cmd ab", Arrays.asList("--ab", "--ac")),
Arguments.of("cmd ac", Arrays.asList("--ab", "--ac")),
// nothing to complete for ab option
Arguments.of("cmd --ab ", Collections.emptyList()),
// nothing to complete for ab option -> giving others options
Arguments.of("cmd --ab ", Collections.singletonList("--ac")),
// used complete adapter
Arguments.of("cmd --ac ", Arrays.asList("cp1", "cp2")),
Arguments.of("cmd --ac ", Arrays.asList("--ab", "cp1", "cp2")),
// propose only not used options
Arguments.of("cmd --ac cp1 ", Collections.singletonList("--ab")),
// after pipe, complete with post processors
Expand Down

0 comments on commit c7a3335

Please sign in to comment.