Skip to content
Open
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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*.{java,xml,gradle}]
indent_style = tab
indent_size = 4
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 the original author or authors.
* Copyright 2016-present 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 @@ -29,6 +29,7 @@
* parameters.
*
* @author Eric Bottard
* @author Piotr Olaszewski
*/
public class EnumValueProvider implements ValueProvider {

Expand All @@ -37,7 +38,7 @@ public List<CompletionProposal> complete(CompletionContext completionContext) {
List<CompletionProposal> result = new ArrayList<>();
CommandOption commandOption = completionContext.getCommandOption();
if (commandOption != null) {
ResolvableType type = commandOption.getType();
ResolvableType type = commandOption.type();
if (type != null) {
Class<?> clazz = type.getRawClass();
if (clazz != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-present 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 @@ -24,6 +24,7 @@
* Represents the input buffer to the shell.
*
* @author Eric Bottard
* @author Piotr Olaszewski
*/
public interface Input {

Expand All @@ -42,7 +43,7 @@ public interface Input {
* single "word")
*/
default List<String> words() {
return "".equals(rawText()) ? Collections.emptyList() : Arrays.asList(rawText().split(" "));
return rawText().isEmpty() ? Collections.emptyList() : Arrays.asList(rawText().split(" "));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 the original author or authors.
* Copyright 2022-present 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 @@ -20,202 +20,13 @@
import org.springframework.shell.core.completion.CompletionResolver;

/**
* Interface representing an option in a command.
* Represents an option of a command.
*
* @author Janne Valkealahti
* @author Piotr Olaszewski
*/
// TODO this is better defined as a record.
public interface CommandOption {

/**
* Gets a long name of an option.
* @return long name of an option
*/
String getLongName();

/**
* Gets a modified long names of an option. Set within a command registration if
* option name modifier were used to have an info about original names.
* @return modified long names of an option
*/
String getLongNameModified();

/**
* Gets a short names of an option.
* @return short names of an option
*/
Character getShortName();

/**
* Gets a description of an option.
* @return description of an option
*/
@Nullable String getDescription();

/**
* Gets a {@link ResolvableType} of an option.
* @return type of an option
*/
@Nullable ResolvableType getType();

/**
* Gets a flag if option is required.
* @return the required flag
*/
boolean isRequired();

/**
* Gets a default value of an option.
* @return the default value
*/
@Nullable String getDefaultValue();

/**
* Gets a positional value.
* @return the positional value
*/
int getPosition();

/**
* Gets a minimum arity.
* @return the minimum arity
*/
int getArityMin();

/**
* Gets a maximum arity.
* @return the maximum arity
*/
int getArityMax();

/**
* Gets a completion function.
* @return the completion function
*/
@Nullable CompletionResolver getCompletion();

/**
* Gets an instance of a default {@link CommandOption}.
* @param longName the long name
* @param longNameModified the modified long name
* @param shortName the short name
* @param description the description
* @param type the type
* @param required the required flag
* @param defaultValue the default value
* @param position the position value
* @param arityMin the min arity
* @param arityMax the max arity
* @param label the label
* @param completion the completion
* @return default command option
*/
static CommandOption of(String longName, String longNameModified, Character shortName, String description,
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
Integer arityMax, String label, CompletionResolver completion) {
return new DefaultCommandOption(longName, longNameModified, shortName, description, type, required,
defaultValue, position, arityMin, arityMax, label, completion);
}

/**
* Default implementation of {@link CommandOption}.
*/
class DefaultCommandOption implements CommandOption {

private String longName;

private String longNameModified;

private Character shortName;

private String description;

private ResolvableType type;

private boolean required;

private String defaultValue;

private int position;

private int arityMin;

private int arityMax;

private CompletionResolver completion;

public DefaultCommandOption(String longName, String longNameModified, Character shortName, String description,
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
Integer arityMax, String label, CompletionResolver completion) {
this.longName = longName;
this.longNameModified = longNameModified;
this.shortName = shortName;
this.description = description;
this.type = type;
this.required = required;
this.defaultValue = defaultValue;
this.position = position != null && position > -1 ? position : -1;
this.arityMin = arityMin != null ? arityMin : -1;
this.arityMax = arityMax != null ? arityMax : -1;
this.completion = completion;
}

@Override
public String getLongName() {
return longName;
}

@Override
public String getLongNameModified() {
return longNameModified;
}

@Override
public Character getShortName() {
return shortName;
}

@Override
public String getDescription() {
return description;
}

@Override
public ResolvableType getType() {
return type;
}

@Override
public boolean isRequired() {
return required;
}

@Override
public String getDefaultValue() {
return defaultValue;
}

@Override
public int getPosition() {
return position;
}

@Override
public int getArityMin() {
return arityMin;
}

@Override
public int getArityMax() {
return arityMax;
}

@Override
public CompletionResolver getCompletion() {
return completion;
}

}
public record CommandOption(String longName, String longNameModified, Character shortName, @Nullable String description,
@Nullable ResolvableType type, boolean required, @Nullable String defaultValue, int position, int arityMin,
int arityMax, @Nullable String label, @Nullable CompletionResolver completion) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package org.springframework.shell.core.command;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jspecify.annotations.Nullable;
Expand All @@ -38,6 +41,8 @@ public class CommandRegistry implements SmartInitializingSingleton, ApplicationC

private final Set<Command> commands;

private final CommandTree.Node root = new CommandTree.Node();

@SuppressWarnings("NullAway.Init")
private ApplicationContext applicationContext;

Expand All @@ -53,31 +58,76 @@ public Set<Command> getCommands() {
return Set.copyOf(commands);
}

@Nullable public Command getCommandByName(String name) {
public @Nullable Command getCommandByName(String name) {
return commands.stream().filter(command -> command.getName().equals(name)).findFirst().orElse(null);
}

public void registerCommand(Command command) {
commands.add(command);
}
public @Nullable Command lookupCommand(List<String> args) {
Command command = null;

public void unregisterCommand(Command command) {
commands.remove(command);
}
CommandTree.Node current = root;
for (String arg : args) {
CommandTree.Node next = current.getChild().get(arg);
if (next == null) {
return command;
}
current = next;

Command cmd = current.getCommand();
if (cmd != null) {
command = cmd;
}
}

public void clearCommands() {
commands.clear();
return command;
}

@Override
public void afterSingletonsInstantiated() {
Collection<Command> commandCollection = this.applicationContext.getBeansOfType(Command.class).values();
Collection<Command> commandCollection = applicationContext.getBeansOfType(Command.class).values();
commands.addAll(commandCollection);

for (Command command : commandCollection) {
CommandTree.Node current = root;
String[] names = command.getName().split(" ");
for (String name : names) {
current = current.child(name);
}
current.setCommand(command);
}
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

private static class CommandTree {

private static class Node {

private final Map<String, Node> child = new HashMap<>();

private @Nullable Command command;

private Map<String, Node> getChild() {
return child;
}

private Node child(String name) {
return child.computeIfAbsent(name, n -> new Node());
}

private @Nullable Command getCommand() {
return command;
}

private void setCommand(Command command) {
this.command = command;
}

}

}

}
Loading