Implement command completion in console for openhab command (#3111)
* Implement command completion in console for openhab command And subcommands can implement it if they want. At the least the CommandConsoleExtension's description will now be visible in tab-completion. I've also implemented detailed completion for openhab:items, openhab:status, openhab:send, and openhab:update. Signed-off-by: Cody Cutrer <cody@cutrer.us>pull/3129/head
parent
73737e255a
commit
e86f388a5b
|
@ -82,8 +82,8 @@ public class CommandWrapper implements Command, Action {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Completer getCompleter(boolean arg0) {
|
||||
return null;
|
||||
public Completer getCompleter(boolean scoped) {
|
||||
return new CompleterWrapper(command, scoped);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console.karaf.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.karaf.shell.api.console.Candidate;
|
||||
import org.apache.karaf.shell.api.console.CommandLine;
|
||||
import org.apache.karaf.shell.api.console.Completer;
|
||||
import org.apache.karaf.shell.api.console.Session;
|
||||
import org.apache.karaf.shell.support.completers.StringsCompleter;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault({})
|
||||
public class CompleterWrapper implements Completer {
|
||||
|
||||
private final @Nullable ConsoleCommandCompleter completer;
|
||||
private final String command;
|
||||
private final String commandDescription;
|
||||
private final @Nullable String globalCommand;
|
||||
|
||||
public CompleterWrapper(final ConsoleCommandExtension command, boolean scoped) {
|
||||
this.completer = command.getCompleter();
|
||||
this.command = command.getCommand();
|
||||
this.commandDescription = command.getDescription();
|
||||
if (!scoped) {
|
||||
globalCommand = CommandWrapper.SCOPE + ":" + this.command;
|
||||
} else {
|
||||
globalCommand = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int complete(Session session, CommandLine commandLine, List<String> candidates) {
|
||||
String localGlobalCommand = globalCommand;
|
||||
if (commandLine.getCursorArgumentIndex() == 0) {
|
||||
StringsCompleter stringsCompleter = new StringsCompleter();
|
||||
stringsCompleter.getStrings().add(command);
|
||||
if (localGlobalCommand != null) {
|
||||
stringsCompleter.getStrings().add(localGlobalCommand);
|
||||
}
|
||||
return stringsCompleter.complete(session, commandLine, candidates);
|
||||
}
|
||||
|
||||
if (commandLine.getArguments().length > 1) {
|
||||
String arg = commandLine.getArguments()[0];
|
||||
if (!arg.equals(command) && !arg.equals(localGlobalCommand))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (commandLine.getCursorArgumentIndex() < 0)
|
||||
return -1;
|
||||
|
||||
var localCompleter = completer;
|
||||
if (localCompleter == null)
|
||||
return -1;
|
||||
|
||||
String[] args = commandLine.getArguments();
|
||||
boolean result = localCompleter.complete(Arrays.copyOfRange(args, 1, args.length),
|
||||
commandLine.getCursorArgumentIndex() - 1, commandLine.getArgumentPosition(), candidates);
|
||||
return result ? commandLine.getBufferPosition() - commandLine.getArgumentPosition() : -1;
|
||||
}
|
||||
|
||||
// Override this method to give command descriptions if completing the command name
|
||||
@Override
|
||||
public void completeCandidates(Session session, CommandLine commandLine, List<Candidate> candidates) {
|
||||
if (commandLine.getCursorArgumentIndex() == 0) {
|
||||
String arg = commandLine.getArguments()[0];
|
||||
arg = arg.substring(0, commandLine.getArgumentPosition());
|
||||
|
||||
if (command.startsWith(arg))
|
||||
candidates.add(new Candidate(command, command, null, commandDescription, null, null, true));
|
||||
String localGlobalCommand = globalCommand;
|
||||
if (localGlobalCommand != null && localGlobalCommand.startsWith(arg))
|
||||
candidates.add(new Candidate(localGlobalCommand, localGlobalCommand, null, commandDescription, null,
|
||||
null, true));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
org.apache.karaf.shell.api.console.Completer.super.completeCandidates(session, commandLine, candidates);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console.karaf.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.karaf.shell.api.console.Candidate;
|
||||
import org.apache.karaf.shell.api.console.CommandLine;
|
||||
import org.apache.karaf.shell.api.console.Completer;
|
||||
import org.apache.karaf.shell.api.console.Session;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
|
||||
/**
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@NonNullByDefault
|
||||
public class CompleterWrapperTest {
|
||||
private @Mock @NonNullByDefault({}) ConsoleCommandExtension commandExtension;
|
||||
private @Mock @NonNullByDefault({}) ConsoleCommandCompleter completer;
|
||||
private @Mock @NonNullByDefault({}) Session session;
|
||||
private @Mock @NonNullByDefault({}) CommandLine commandLine;
|
||||
private @NonNullByDefault({}) CommandWrapper commandWrapper;
|
||||
private @NonNullByDefault({}) Completer completerWrapper;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(commandExtension.getCommand()).thenReturn("command");
|
||||
when(commandExtension.getCompleter()).thenReturn(completer);
|
||||
when(commandExtension.getDescription()).thenReturn("description");
|
||||
commandWrapper = new CommandWrapper(commandExtension);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fillsCommandDescriptionsLocalOnly() {
|
||||
completerWrapper = commandWrapper.getCompleter(true);
|
||||
var candidates = new ArrayList<Candidate>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(0);
|
||||
when(commandLine.getArgumentPosition()).thenReturn(0);
|
||||
|
||||
completerWrapper.completeCandidates(session, commandLine, candidates);
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("command", candidates.get(0).value());
|
||||
assertEquals("description", candidates.get(0).descr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fillsCommandDescriptionsLocalAndGlobal() {
|
||||
completerWrapper = commandWrapper.getCompleter(false);
|
||||
var candidates = new ArrayList<Candidate>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(0);
|
||||
when(commandLine.getArgumentPosition()).thenReturn(0);
|
||||
|
||||
completerWrapper.completeCandidates(session, commandLine, candidates);
|
||||
assertEquals(2, candidates.size());
|
||||
assertEquals("command", candidates.get(0).value());
|
||||
assertEquals("description", candidates.get(0).descr());
|
||||
|
||||
assertEquals("openhab:command", candidates.get(1).value());
|
||||
assertEquals("description", candidates.get(1).descr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeCandidatesCompletesArguments() {
|
||||
completerWrapper = commandWrapper.getCompleter(true);
|
||||
var candidates = new ArrayList<Candidate>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "command", "subcmd" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(1);
|
||||
when(commandLine.getArgumentPosition()).thenReturn(6);
|
||||
when(commandLine.getBufferPosition()).thenReturn(14);
|
||||
when(completer.complete(new String[] { "subcmd" }, 0, 6, new ArrayList<>())).thenReturn(false);
|
||||
|
||||
completerWrapper.completeCandidates(session, commandLine, candidates);
|
||||
assertTrue(candidates.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesntCallCompleterForOtherCommands() {
|
||||
completerWrapper = commandWrapper.getCompleter(true);
|
||||
var candidates = new ArrayList<String>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "somethingElse", "" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(1);
|
||||
verifyNoInteractions(completer);
|
||||
|
||||
assertEquals(-1, completerWrapper.complete(session, commandLine, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callsCompleterWithProperlyScopedArguments() {
|
||||
completerWrapper = commandWrapper.getCompleter(true);
|
||||
var candidates = new ArrayList<String>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "command", "subcmd" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(1);
|
||||
when(commandLine.getArgumentPosition()).thenReturn(6);
|
||||
when(completer.complete(new String[] { "subcmd" }, 0, 6, new ArrayList<>())).thenReturn(false);
|
||||
|
||||
assertEquals(-1, completerWrapper.complete(session, commandLine, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callsCompleterForGlobalForm() {
|
||||
completerWrapper = commandWrapper.getCompleter(false);
|
||||
var candidates = new ArrayList<String>();
|
||||
when(commandLine.getArguments()).thenReturn(new String[] { "openhab:command", "subcmd" });
|
||||
when(commandLine.getCursorArgumentIndex()).thenReturn(1);
|
||||
when(commandLine.getArgumentPosition()).thenReturn(6);
|
||||
when(completer.complete(new String[] { "subcmd" }, 0, 6, new ArrayList<>())).thenReturn(false);
|
||||
|
||||
assertEquals(-1, completerWrapper.complete(session, commandLine, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Implementing this interface allows a {@link ConsoleCommandExtension} to
|
||||
* provide completions for the user as they write commands.
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ConsoleCommandCompleter {
|
||||
/**
|
||||
* Populate possible completion candidates.
|
||||
*
|
||||
* @param args An array of all arguments to be passed to the ConsoleCommandExtension's execute method
|
||||
* @param cursorArgumentIndex the argument index the cursor is currently in
|
||||
* @param cursorPosition the position of the cursor within the argument
|
||||
* @param candidates a list to fill with possible completion candidates
|
||||
* @return if a candidate was found
|
||||
*/
|
||||
boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Completer for a set of strings.
|
||||
*
|
||||
* It will provide candidate completions for whichever argument the cursor is located in.
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringsCompleter implements ConsoleCommandCompleter {
|
||||
private final SortedSet<String> strings;
|
||||
private final boolean caseSensitive;
|
||||
|
||||
public StringsCompleter() {
|
||||
this(List.of(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strings The set of valid strings to be completed
|
||||
* @param caseSensitive if strings must match case sensitively when the user is typing them
|
||||
*/
|
||||
public StringsCompleter(final Collection<String> strings, boolean caseSensitive) {
|
||||
this.strings = new TreeSet<>(caseSensitive ? String::compareTo : String::compareToIgnoreCase);
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.strings.addAll(strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strings that are allowed for this completer, so that you can modify the set.
|
||||
*/
|
||||
public SortedSet<String> getStrings() {
|
||||
return strings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
String argument;
|
||||
if (cursorArgumentIndex >= 0 && cursorArgumentIndex < args.length) {
|
||||
argument = args[cursorArgumentIndex].substring(0, cursorPosition);
|
||||
} else {
|
||||
argument = "";
|
||||
}
|
||||
|
||||
if (!caseSensitive) {
|
||||
argument = argument.toLowerCase();
|
||||
}
|
||||
|
||||
SortedSet<String> matches = getStrings().tailSet(argument);
|
||||
|
||||
for (String match : matches) {
|
||||
String s = caseSensitive ? match : match.toLowerCase();
|
||||
if (!s.startsWith(argument)) {
|
||||
break;
|
||||
}
|
||||
|
||||
candidates.add(match + " ");
|
||||
}
|
||||
|
||||
return !candidates.isEmpty();
|
||||
}
|
||||
}
|
|
@ -15,7 +15,9 @@ package org.openhab.core.io.console.extensions;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
|
||||
/**
|
||||
* Client which provide a console command have to implement this interface
|
||||
|
@ -53,4 +55,14 @@ public interface ConsoleCommandExtension {
|
|||
* @return the help texts for this extension
|
||||
*/
|
||||
List<String> getUsages();
|
||||
|
||||
/**
|
||||
* This method allows a {@link ConsoleCommandExtension} to provide an object to enable
|
||||
* tab-completion functionality for the user.
|
||||
*
|
||||
* @return a {@link ConsoleCommandCompleter} object for this command
|
||||
*/
|
||||
default @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console.internal.extension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemNotUniqueException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
|
||||
/**
|
||||
* Console command completer for send and update
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ItemConsoleCommandCompleter implements ConsoleCommandCompleter {
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final @Nullable Function<Item, Class<?>[]> dataTypeGetter;
|
||||
|
||||
public ItemConsoleCommandCompleter(ItemRegistry itemRegistry) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.dataTypeGetter = null;
|
||||
}
|
||||
|
||||
public ItemConsoleCommandCompleter(ItemRegistry itemRegistry, Function<Item, Class<?>[]> dataTypeGetter) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.dataTypeGetter = dataTypeGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return new StringsCompleter(
|
||||
itemRegistry.getAll().stream().map(i -> i.getName()).collect(Collectors.toList()), true)
|
||||
.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
var localDataTypeGetter = dataTypeGetter;
|
||||
if (cursorArgumentIndex == 1 && localDataTypeGetter != null) {
|
||||
try {
|
||||
Item item = itemRegistry.getItemByPattern(args[0]);
|
||||
Stream<Class<?>> enums = Stream.of(localDataTypeGetter.apply(item)).filter(Class::isEnum);
|
||||
Stream<? super Enum<?>> enumConstants = enums.flatMap(
|
||||
t -> Stream.of(Objects.requireNonNull(((Class<? extends Enum<?>>) t).getEnumConstants())));
|
||||
return new StringsCompleter(enumConstants.map(Object::toString).collect(Collectors.toList()), false)
|
||||
.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} catch (ItemNotFoundException | ItemNotUniqueException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,9 +17,13 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
|
@ -48,6 +52,42 @@ public class ItemConsoleCommandExtension extends AbstractConsoleCommandExtension
|
|||
private static final String SUBCMD_REMOVE = "remove";
|
||||
private static final String SUBCMD_ADDTAG = "addTag";
|
||||
private static final String SUBCMD_RMTAG = "rmTag";
|
||||
private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(
|
||||
List.of(SUBCMD_LIST, SUBCMD_CLEAR, SUBCMD_REMOVE, SUBCMD_ADDTAG, SUBCMD_RMTAG), false);
|
||||
|
||||
private class ItemConsoleCommandCompleter implements ConsoleCommandCompleter {
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
if (cursorArgumentIndex == 1) {
|
||||
Collection<Item> items;
|
||||
switch (args[0]) {
|
||||
case SUBCMD_ADDTAG:
|
||||
case SUBCMD_RMTAG:
|
||||
items = managedItemProvider.getAll();
|
||||
break;
|
||||
case SUBCMD_REMOVE:
|
||||
items = itemRegistry.getAll();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return new StringsCompleter(items.stream().map(i -> i.getName()).collect(Collectors.toList()), true)
|
||||
.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
if (cursorArgumentIndex == 2 && args[0].equals(SUBCMD_RMTAG)) {
|
||||
Item item = managedItemProvider.get(args[1]);
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
return new StringsCompleter(item.getTags(), true).complete(args, cursorArgumentIndex, cursorPosition,
|
||||
candidates);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final ManagedItemProvider managedItemProvider;
|
||||
|
@ -129,6 +169,11 @@ public class ItemConsoleCommandExtension extends AbstractConsoleCommandExtension
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new ItemConsoleCommandCompleter();
|
||||
}
|
||||
|
||||
private <T> void handleTags(final Consumer<T> func, final T tag, GenericItem gItem, Console console) {
|
||||
// allow adding/removing of tags only for managed items
|
||||
if (managedItemProvider.get(gItem.getName()) != null) {
|
||||
|
|
|
@ -13,10 +13,13 @@
|
|||
package org.openhab.core.io.console.internal.extension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.items.Item;
|
||||
|
@ -78,7 +81,7 @@ public class SendConsoleCommandExtension extends AbstractConsoleCommandExtension
|
|||
console.print(" " + acceptedType.getSimpleName());
|
||||
if (acceptedType.isEnum()) {
|
||||
console.print(": ");
|
||||
for (Object e : acceptedType.getEnumConstants()) {
|
||||
for (Object e : Objects.requireNonNull(acceptedType.getEnumConstants())) {
|
||||
console.print(e + " ");
|
||||
}
|
||||
}
|
||||
|
@ -100,4 +103,10 @@ public class SendConsoleCommandExtension extends AbstractConsoleCommandExtension
|
|||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new ItemConsoleCommandCompleter(itemRegistry,
|
||||
(Item i) -> i.getAcceptedCommandTypes().toArray(Class<?>[]::new));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ package org.openhab.core.io.console.internal.extension;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.items.Item;
|
||||
|
@ -69,4 +71,9 @@ public class StatusConsoleCommandExtension extends AbstractConsoleCommandExtensi
|
|||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new ItemConsoleCommandCompleter(itemRegistry);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ package org.openhab.core.io.console.internal.extension;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.items.Item;
|
||||
|
@ -93,4 +95,10 @@ public class UpdateConsoleCommandExtension extends AbstractConsoleCommandExtensi
|
|||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new ItemConsoleCommandCompleter(itemRegistry,
|
||||
(Item i) -> i.getAcceptedDataTypes().toArray(Class<?>[]::new));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringsCompleterTest {
|
||||
@Test
|
||||
public void completeSimple() {
|
||||
var sc = new StringsCompleter(List.of("def", "abc", "ghi"), false);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
// positive match
|
||||
assertTrue(sc.complete(new String[] { "a" }, 0, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("abc ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
// negative match
|
||||
assertFalse(sc.complete(new String[] { "z" }, 0, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
// case insensitive
|
||||
assertTrue(sc.complete(new String[] { "A" }, 0, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("abc ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
// second argument
|
||||
assertTrue(sc.complete(new String[] { "a", "d" }, 1, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("def ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
// cursor not at end of word (truncates rest)
|
||||
assertTrue(sc.complete(new String[] { "a", "dg" }, 1, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("def ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
// first argument when second is present
|
||||
assertTrue(sc.complete(new String[] { "a", "d" }, 0, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("abc ", candidates.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void caseSensitive() {
|
||||
var sc = new StringsCompleter(List.of("dEf", "ABc", "ghi"), true);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertFalse(sc.complete(new String[] { "D" }, 0, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertFalse(sc.complete(new String[] { "ab" }, 0, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertTrue(sc.complete(new String[] { "AB" }, 0, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("ABc ", candidates.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleCandidates() {
|
||||
var sc = new StringsCompleter(List.of("abcde", "bcde", "abcdef", "abcdd", "abcdee", "abcdf"), false);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertTrue(sc.complete(new String[] { "abcd" }, 0, 4, candidates));
|
||||
assertEquals(5, candidates.size());
|
||||
assertEquals("abcdd ", candidates.get(0));
|
||||
assertEquals("abcde ", candidates.get(1));
|
||||
assertEquals("abcdee ", candidates.get(2));
|
||||
assertEquals("abcdef ", candidates.get(3));
|
||||
assertEquals("abcdf ", candidates.get(4));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(sc.complete(new String[] { "abcde" }, 0, 5, candidates));
|
||||
assertEquals(3, candidates.size());
|
||||
assertEquals("abcde ", candidates.get(0));
|
||||
assertEquals("abcdee ", candidates.get(1));
|
||||
assertEquals("abcdef ", candidates.get(2));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(sc.complete(new String[] { "abcdee" }, 0, 6, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("abcdee ", candidates.get(0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console.internal.extension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemNotUniqueException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
|
||||
/**
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@NonNullByDefault
|
||||
public class ItemConsoleCommandCompleterTest {
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
List<Item> items = List.of(new SwitchItem("Item1"), new SwitchItem("Item2"), new SwitchItem("JItem1"));
|
||||
when(itemRegistryMock.getAll()).thenReturn(items);
|
||||
}
|
||||
|
||||
private void mockGetItemByPattern() throws ItemNotFoundException, ItemNotUniqueException {
|
||||
when(itemRegistryMock.getItemByPattern(anyString())).thenAnswer(invocation -> {
|
||||
switch ((String) invocation.getArguments()[0]) {
|
||||
case "Item1":
|
||||
return itemRegistryMock.getAll().iterator().next();
|
||||
default:
|
||||
throw new ItemNotFoundException("It");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeItems() throws ItemNotFoundException, ItemNotUniqueException {
|
||||
var completer = new ItemConsoleCommandCompleter(itemRegistryMock);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "It" }, 0, 2, candidates));
|
||||
assertEquals(2, candidates.size());
|
||||
assertEquals("Item1 ", candidates.get(0));
|
||||
assertEquals("Item2 ", candidates.get(1));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "JI" }, 0, 2, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("JItem1 ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
// case sensitive
|
||||
assertFalse(completer.complete(new String[] { "it" }, 0, 2, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
// doesn't complete anything when we're not referring to the current argument
|
||||
assertFalse(completer.complete(new String[] { "It", "It" }, 1, 2, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
// doesn't complete anything for the second argument
|
||||
assertFalse(completer.complete(new String[] { "Item1", "" }, 1, 0, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeSend() throws ItemNotFoundException, ItemNotUniqueException {
|
||||
var completer = new ItemConsoleCommandCompleter(itemRegistryMock,
|
||||
i -> i.getAcceptedCommandTypes().toArray(Class<?>[]::new));
|
||||
var candidates = new ArrayList<String>();
|
||||
mockGetItemByPattern();
|
||||
|
||||
// Can't find the item; no commands at all
|
||||
assertFalse(completer.complete(new String[] { "It", "O" }, 1, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertTrue(completer.complete(new String[] { "Item1", "" }, 1, 0, candidates));
|
||||
assertEquals(3, candidates.size());
|
||||
assertEquals("OFF ", candidates.get(0));
|
||||
assertEquals("ON ", candidates.get(1));
|
||||
assertEquals("REFRESH ", candidates.get(2));
|
||||
candidates.clear();
|
||||
|
||||
// case insensitive
|
||||
assertTrue(completer.complete(new String[] { "Item1", "o" }, 1, 1, candidates));
|
||||
assertEquals(2, candidates.size());
|
||||
assertEquals("OFF ", candidates.get(0));
|
||||
assertEquals("ON ", candidates.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeUpdate() throws ItemNotFoundException, ItemNotUniqueException {
|
||||
var completer = new ItemConsoleCommandCompleter(itemRegistryMock,
|
||||
i -> i.getAcceptedDataTypes().toArray(Class<?>[]::new));
|
||||
var candidates = new ArrayList<String>();
|
||||
mockGetItemByPattern();
|
||||
|
||||
// Can't find the item; no commands at all
|
||||
assertFalse(completer.complete(new String[] { "It", "O" }, 1, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertTrue(completer.complete(new String[] { "Item1", "" }, 1, 0, candidates));
|
||||
assertEquals(4, candidates.size());
|
||||
assertEquals("NULL ", candidates.get(0));
|
||||
assertEquals("OFF ", candidates.get(1));
|
||||
assertEquals("ON ", candidates.get(2));
|
||||
assertEquals("UNDEF ", candidates.get(3));
|
||||
candidates.clear();
|
||||
|
||||
// case insensitive
|
||||
assertTrue(completer.complete(new String[] { "Item1", "o" }, 1, 1, candidates));
|
||||
assertEquals(2, candidates.size());
|
||||
assertEquals("OFF ", candidates.get(0));
|
||||
assertEquals("ON ", candidates.get(1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.console.internal.extension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ManagedItemProvider;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
|
||||
/**
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@NonNullByDefault
|
||||
public class ItemConsoleCommandExtensionTest {
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) ManagedItemProvider managedItemProviderMock;
|
||||
private @NonNullByDefault({}) ConsoleCommandCompleter completer;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
completer = new ItemConsoleCommandExtension(itemRegistryMock, managedItemProviderMock).getCompleter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeSubcommands() {
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "" }, 0, 0, candidates));
|
||||
assertEquals(5, candidates.size());
|
||||
assertEquals("addTag ", candidates.get(0));
|
||||
assertEquals("clear ", candidates.get(1));
|
||||
assertEquals("list ", candidates.get(2));
|
||||
assertEquals("remove ", candidates.get(3));
|
||||
assertEquals("rmTag ", candidates.get(4));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "A", "Item1" }, 0, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("addTag ", candidates.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeManagedItems() {
|
||||
List<Item> items = List.of(new SwitchItem("Item1"));
|
||||
when(managedItemProviderMock.getAll()).thenReturn(items);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertFalse(completer.complete(new String[] { "bogus", "I" }, 1, 1, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertTrue(completer.complete(new String[] { "addTag", "I" }, 0, 6, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("addTag ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "addTag", "I" }, 1, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("Item1 ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "rmTag", "I" }, 1, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("Item1 ", candidates.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeAllItems() {
|
||||
List<Item> items = List.of(new SwitchItem("Item2"));
|
||||
when(itemRegistryMock.getAll()).thenReturn(items);
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "remove", "I" }, 0, 6, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("remove ", candidates.get(0));
|
||||
candidates.clear();
|
||||
|
||||
assertTrue(completer.complete(new String[] { "remove", "I" }, 1, 1, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("Item2 ", candidates.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeRmTag() {
|
||||
var item3 = new SwitchItem("Item3");
|
||||
var item4 = new SwitchItem("Item4");
|
||||
item3.addTag("Tag1");
|
||||
when(managedItemProviderMock.get(anyString())).thenAnswer(invocation -> {
|
||||
switch ((String) invocation.getArguments()[0]) {
|
||||
case "Item3":
|
||||
return item3;
|
||||
case "Item4":
|
||||
return item4;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
var candidates = new ArrayList<String>();
|
||||
|
||||
// wrong sub-command
|
||||
assertFalse(completer.complete(new String[] { "addTag", "Item3", "" }, 2, 0, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
// Item doesn't exist
|
||||
assertFalse(completer.complete(new String[] { "rmTag", "Item2", "" }, 2, 0, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
// Item has no tags
|
||||
assertFalse(completer.complete(new String[] { "rmTag", "Item4", "" }, 2, 0, candidates));
|
||||
assertTrue(candidates.isEmpty());
|
||||
|
||||
assertTrue(completer.complete(new String[] { "rmTag", "Item3", "" }, 2, 0, candidates));
|
||||
assertEquals(1, candidates.size());
|
||||
assertEquals("Tag1 ", candidates.get(0));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue