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
Cody Cutrer 2022-10-22 02:52:31 -06:00 committed by GitHub
parent 73737e255a
commit e86f388a5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 898 additions and 3 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}